@m-kopa/launchpad-cli 0.24.0 → 0.25.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.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,27 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
  This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html);
7
7
  pre-1.0 minor bumps may carry breaking changes per ADR 0005.
8
8
 
9
+ ## 0.25.0 — 2026-06-11
10
+
11
+ ### Changed
12
+
13
+ - **Redeploying an unchanged app is a clean no-op** (sp-devlp1 AC1 / ADR
14
+ 0025). The bot diffs the bundle against the managed repo's `main` (git
15
+ tree-diff) before gating: nothing changed → `launchpad deploy` prints
16
+ "Nothing to deploy" and exits 0 — no policy failure, no junk commit, no
17
+ rebuild. Deploys that DO change files commit only the changed files.
18
+ - **Policy gates judge what you changed, not what you have** (AC2).
19
+ Already-live files are never re-flagged; their pre-existing violations
20
+ surface as a non-blocking drift report (and in `launchpad status`'s new
21
+ standing-exceptions block). Secrets are the exception: a secret anywhere
22
+ in the upload — including one already on `main` — always blocks, with
23
+ rotation guidance.
24
+ - **`deploy --apply` no longer requires local git state** (AC3). The bot
25
+ resolves `launchpad.yaml` at the managed repo's `main` HEAD; your local
26
+ history can share zero commits with the managed repo. New `--at <sha>`
27
+ pins a specific managed-repo commit; `rollback` now uses it too (fixing
28
+ its local-HEAD keying).
29
+
9
30
  ## 0.24.0 — 2026-06-11
10
31
 
11
32
  ### Changed
@@ -1,5 +1,17 @@
1
1
  import type { CliConfig } from "../config.js";
2
2
  import type { WorkerArtifact } from "./cron-bundle.js";
3
+ /** Drift report riding on a success response (sp-devlp1 T3 / D2):
4
+ * policy violations observed in content already live on managed
5
+ * main — non-blocking, recorded server-side in the queryable
6
+ * standing-exception inventory. */
7
+ export interface StandingExceptionsSummary {
8
+ readonly count: number;
9
+ readonly entries: ReadonlyArray<{
10
+ readonly path: string;
11
+ readonly rule: string;
12
+ }>;
13
+ readonly message?: string;
14
+ }
3
15
  /**
4
16
  * Response shape from the bot's deploy-bundle endpoint when the
5
17
  * bundle was committed to the per-app repo (HTTP 202, status="accepted").
@@ -10,6 +22,26 @@ export interface DeployBundleAccepted {
10
22
  readonly commit_sha: string;
11
23
  readonly repo: string;
12
24
  readonly message: string;
25
+ /** "delta" (gates judged the changed-set) or "full" (fallback). */
26
+ readonly gating?: "delta" | "full";
27
+ readonly committed_file_count?: number;
28
+ /** Files the bot's server-side app-boundary stripped (inferred
29
+ * contract, never-shippable paths). */
30
+ readonly boundary_stripped?: readonly string[];
31
+ readonly standing_exceptions?: StandingExceptionsSummary;
32
+ }
33
+ /**
34
+ * Response shape when managed main already matches the upload
35
+ * byte-for-byte (HTTP 200, sp-devlp1 AC1): no commit was created,
36
+ * no CF Pages build was triggered.
37
+ */
38
+ export interface DeployBundleNothingToDeploy {
39
+ readonly outcome: "nothing-to-deploy";
40
+ readonly slug: string;
41
+ readonly head_sha: string;
42
+ readonly message: string;
43
+ readonly boundary_stripped?: readonly string[];
44
+ readonly standing_exceptions?: StandingExceptionsSummary;
13
45
  }
14
46
  /**
15
47
  * Response shape when the slug was never registered before and the
@@ -27,8 +59,8 @@ export interface DeployBundleProvisioningStarted {
27
59
  readonly appType: string;
28
60
  readonly message: string;
29
61
  }
30
- /** Either 202 success shape. */
31
- export type DeployBundleSuccess = DeployBundleAccepted | DeployBundleProvisioningStarted;
62
+ /** Any success shape (202 accepted / 202 provisioning / 200 no-op). */
63
+ export type DeployBundleSuccess = DeployBundleAccepted | DeployBundleProvisioningStarted | DeployBundleNothingToDeploy;
32
64
  /** Structured error shape from the bot on validation failure. */
33
65
  export interface DeployBundleError {
34
66
  readonly error: string;
@@ -1 +1 @@
1
- {"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../../src/bundle/upload.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAgBvD;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,CAAC,MAAM,EAAE,sBAAsB,CAAC;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,gCAAgC;AAChC,MAAM,MAAM,mBAAmB,GAC3B,oBAAoB,GACpB,+BAA+B,CAAC;AAEpC,iEAAiE;AACjE,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnE,QAAQ,CAAC,UAAU,CAAC,EAAE,aAAa,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACrF,QAAQ,CAAC,QAAQ,CAAC,EAAE,aAAa,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACnE;AAED,MAAM,MAAM,kBAAkB,GAC1B;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,mBAAmB,CAAA;CAAE,GAC7C;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,iBAAiB,CAAA;CAAE,CAAC;AAEnE;;;;;;GAMG;AACH,wBAAsB,YAAY,CAChC,GAAG,EAAE,SAAS,EACd,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,UAAU,EACvB,cAAc,CAAC,EAAE,cAAc,GAC9B,OAAO,CAAC,kBAAkB,CAAC,CAsF7B"}
1
+ {"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../../src/bundle/upload.ts"],"names":[],"mappings":"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"}
package/dist/cli.js CHANGED
@@ -19,7 +19,7 @@ var __toESM = (mod, isNodeMode, target) => {
19
19
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
20
20
 
21
21
  // src/version.ts
22
- var CLI_VERSION = "0.24.0";
22
+ var CLI_VERSION = "0.25.0";
23
23
 
24
24
  // src/config.ts
25
25
  import * as os from "node:os";
@@ -1432,6 +1432,9 @@ async function uploadBundle(cfg, slug, manifestYaml, bundleTarGz, workerArtifact
1432
1432
  if (res.status === 202) {
1433
1433
  return { kind: "ok", response: body };
1434
1434
  }
1435
+ if (res.status === 200 && typeof body === "object" && body !== null && body.outcome === "nothing-to-deploy") {
1436
+ return { kind: "ok", response: body };
1437
+ }
1435
1438
  return {
1436
1439
  kind: "error",
1437
1440
  status: res.status,
@@ -3380,6 +3383,7 @@ function parseDeployFlags(args) {
3380
3383
  let yes = false;
3381
3384
  let resumePrNumber = null;
3382
3385
  let timeoutMinutes = null;
3386
+ let atSha = null;
3383
3387
  let modeFlagsSeen = 0;
3384
3388
  let i = 0;
3385
3389
  while (i < args.length) {
@@ -3532,6 +3536,17 @@ function parseDeployFlags(args) {
3532
3536
  i += 2;
3533
3537
  continue;
3534
3538
  }
3539
+ if (a === "--at") {
3540
+ const v = args[i + 1];
3541
+ if (v === undefined)
3542
+ return `missing value for ${a}`;
3543
+ if (!/^[0-9a-f]{40}$/.test(v)) {
3544
+ return `invalid --at "${v}" — expected a 40-char lowercase hex git SHA from the MANAGED repo`;
3545
+ }
3546
+ atSha = v;
3547
+ i += 2;
3548
+ continue;
3549
+ }
3535
3550
  if (a === "--resume-pr") {
3536
3551
  const v = args[i + 1];
3537
3552
  if (v === undefined)
@@ -3601,6 +3616,7 @@ function parseDeployFlags(args) {
3601
3616
  rePin,
3602
3617
  yes,
3603
3618
  resumePrNumber,
3619
+ atSha,
3604
3620
  timeoutMinutes
3605
3621
  },
3606
3622
  message: null,
@@ -3610,6 +3626,9 @@ function parseDeployFlags(args) {
3610
3626
  if (resumePrNumber !== null) {
3611
3627
  return "--resume-pr is only valid with --apply";
3612
3628
  }
3629
+ if (atSha !== null) {
3630
+ return "--at is only valid with --apply";
3631
+ }
3613
3632
  if (timeoutMinutes !== null) {
3614
3633
  return "--timeout-minutes is only valid with --apply";
3615
3634
  }
@@ -3687,7 +3706,6 @@ function parseDeployFlags(args) {
3687
3706
  // src/deploy/apply.ts
3688
3707
  import { existsSync as existsSync3, rmSync } from "node:fs";
3689
3708
  import { resolve as resolvePath, join as join7 } from "node:path";
3690
- import { execFileSync } from "node:child_process";
3691
3709
 
3692
3710
  // src/manifest/load.ts
3693
3711
  import { readFileSync as readFileSync5 } from "node:fs";
@@ -3780,29 +3798,23 @@ async function runDeployApply(opts, io, deps = {}) {
3780
3798
  io.err(`launchpad deploy --apply: manifest's metadata.name '${slug}' is not a valid slug`);
3781
3799
  return 1;
3782
3800
  }
3783
- let manifestSha;
3784
- try {
3785
- manifestSha = (deps.gitHeadSha ?? defaultGitHeadSha)(process.cwd());
3786
- } catch (e) {
3787
- io.err(`launchpad deploy --apply: failed to resolve git HEAD in ${process.cwd()}: ${describe10(e)}`);
3788
- io.err(" Apply needs a clean commit so portal-bot can fetch launchpad.yaml at that sha.");
3789
- return 2;
3790
- }
3791
- if (!MANIFEST_SHA_REGEX.test(manifestSha)) {
3792
- io.err(`launchpad deploy --apply: git HEAD returned an unexpected sha shape: '${manifestSha}'`);
3793
- return 2;
3801
+ if (opts.atSha !== undefined && !MANIFEST_SHA_REGEX.test(opts.atSha)) {
3802
+ io.err(`launchpad deploy --apply: --at expects a 40-char lowercase hex git SHA, got '${opts.atSha}'`);
3803
+ return 64;
3794
3804
  }
3795
- io.out(`Planning apply for ${slug} @ ${manifestSha.slice(0, 7)} …`);
3805
+ io.out(opts.atSha !== undefined ? `Planning apply for ${slug} @ ${opts.atSha.slice(0, 7)} (managed repo) …` : `Planning apply for ${slug} (bot resolves managed main HEAD) …`);
3796
3806
  let plan;
3797
3807
  try {
3798
3808
  plan = await apiJson(cfg, {
3799
3809
  method: "POST",
3800
3810
  path: `/apps/${slug}/manifest/plan`,
3801
- jsonBody: { manifestSha }
3811
+ jsonBody: opts.atSha !== undefined ? { manifestSha: opts.atSha } : {}
3802
3812
  }, fetcher);
3803
3813
  } catch (e) {
3804
3814
  return mapHttpError(e, slug, io);
3805
3815
  }
3816
+ const manifestSha = plan.manifestSha;
3817
+ io.out(`Manifest pinned @ ${manifestSha.slice(0, 7)} (managed repo provenance).`);
3806
3818
  for (const w of plan.warnings)
3807
3819
  io.err(`! ${w}`);
3808
3820
  io.out("");
@@ -3922,14 +3934,6 @@ function sleep(ms) {
3922
3934
  return Promise.resolve();
3923
3935
  return new Promise((res) => setTimeout(res, ms));
3924
3936
  }
3925
- function defaultGitHeadSha(cwd) {
3926
- const out = execFileSync("git", ["rev-parse", "HEAD"], {
3927
- cwd,
3928
- encoding: "utf8",
3929
- stdio: ["ignore", "pipe", "pipe"]
3930
- });
3931
- return out.trim();
3932
- }
3933
3937
  function deletePinIfPresent(cfg, slug, io) {
3934
3938
  const pinPath = join7(cfg.stateDir, slug, "group.json");
3935
3939
  if (!existsSync3(pinPath))
@@ -4012,7 +4016,7 @@ function renderManifestError(loaded, io) {
4012
4016
  }
4013
4017
 
4014
4018
  // src/deploy/dry-run.ts
4015
- import { execFileSync as execFileSync2 } from "node:child_process";
4019
+ import { execFileSync } from "node:child_process";
4016
4020
  import { resolve as resolve5 } from "node:path";
4017
4021
  var MANIFEST_SHA_REGEX2 = /^[0-9a-f]{40}$/;
4018
4022
  async function runDeployDryRun(opts, io, deps = {}) {
@@ -4026,7 +4030,7 @@ async function runDeployDryRun(opts, io, deps = {}) {
4026
4030
  const slug = loaded.manifest.metadata.name;
4027
4031
  let manifestSha;
4028
4032
  try {
4029
- manifestSha = (deps.gitHeadSha ?? defaultGitHeadSha2)(process.cwd());
4033
+ manifestSha = (deps.gitHeadSha ?? defaultGitHeadSha)(process.cwd());
4030
4034
  } catch (e) {
4031
4035
  const msg = `failed to resolve git HEAD in ${process.cwd()}: ${describe11(e)}`;
4032
4036
  if (opts.json) {
@@ -4069,8 +4073,8 @@ async function runDeployDryRun(opts, io, deps = {}) {
4069
4073
  }
4070
4074
  return 0;
4071
4075
  }
4072
- function defaultGitHeadSha2(cwd) {
4073
- const out = execFileSync2("git", ["rev-parse", "HEAD"], {
4076
+ function defaultGitHeadSha(cwd) {
4077
+ const out = execFileSync("git", ["rev-parse", "HEAD"], {
4074
4078
  cwd,
4075
4079
  encoding: "utf8",
4076
4080
  stdio: ["ignore", "pipe", "pipe"]
@@ -4402,6 +4406,7 @@ async function runDeploy(args, io) {
4402
4406
  platformRepo: flags.mode.platformRepo,
4403
4407
  rePin: flags.mode.rePin,
4404
4408
  yes: flags.mode.yes,
4409
+ ...flags.mode.atSha !== null ? { atSha: flags.mode.atSha } : {},
4405
4410
  ...flags.mode.resumePrNumber !== null ? { resumePrNumber: flags.mode.resumePrNumber } : {},
4406
4411
  ...flags.mode.timeoutMinutes !== null ? { timeoutMinutes: flags.mode.timeoutMinutes } : {}
4407
4412
  }, io);
@@ -4597,6 +4602,28 @@ function formatBytes2(n) {
4597
4602
  function describe12(e) {
4598
4603
  return e instanceof Error ? e.message : String(e);
4599
4604
  }
4605
+ function surfaceDeployExtras(body, io, slug) {
4606
+ if (body.boundary_stripped !== undefined && body.boundary_stripped.length > 0) {
4607
+ io.err(`warning: the bot stripped ${body.boundary_stripped.length} never-shippable file(s) server-side:`);
4608
+ for (const p of body.boundary_stripped.slice(0, 10)) {
4609
+ io.err(` - ${p}`);
4610
+ }
4611
+ if (body.boundary_stripped.length > 10) {
4612
+ io.err(` … and ${body.boundary_stripped.length - 10} more`);
4613
+ }
4614
+ }
4615
+ const se = body.standing_exceptions;
4616
+ if (se !== undefined && se.count > 0) {
4617
+ io.out(` ${se.count} standing policy exception(s) observed in content already live on main (non-blocking):`);
4618
+ for (const e of se.entries.slice(0, 5)) {
4619
+ io.out(` - ${e.path} [${e.rule}]`);
4620
+ }
4621
+ if (se.entries.length > 5) {
4622
+ io.out(` … and ${se.entries.length - 5} more`);
4623
+ }
4624
+ io.out(` Full list: \`launchpad status ${slug}\`.`);
4625
+ }
4626
+ }
4600
4627
  function resolveManifestSlug(parsed) {
4601
4628
  if (parsed === null || typeof parsed !== "object" || typeof parsed.metadata !== "object" || parsed.metadata === null) {
4602
4629
  return null;
@@ -4694,10 +4721,19 @@ async function runModelADeploy(args) {
4694
4721
  for (const w of result.boundaryWarnings) {
4695
4722
  io.err(`warning: ${w}`);
4696
4723
  }
4697
- if (result.result.status === "provisioning_started") {
4698
- const submissionId = typeof result.result.submissionId === "string" ? result.result.submissionId : "(missing)";
4699
- const appType = typeof result.result.appType === "string" ? result.result.appType : "(missing)";
4700
- const message = typeof result.result.message === "string" ? result.result.message : "Bot accepted the upload and started provisioning.";
4724
+ const success = result.result;
4725
+ if ("outcome" in success) {
4726
+ io.out("Nothing to deploy your app is already live at this content.");
4727
+ if (typeof success.head_sha === "string") {
4728
+ io.out(` (managed main @ ${success.head_sha.slice(0, 8)} matches the bundle)`);
4729
+ }
4730
+ surfaceDeployExtras(success, io, slug);
4731
+ return 0;
4732
+ }
4733
+ if (success.status === "provisioning_started") {
4734
+ const submissionId = typeof success.submissionId === "string" ? success.submissionId : "(missing)";
4735
+ const appType = typeof success.appType === "string" ? success.appType : "(missing)";
4736
+ const message = typeof success.message === "string" ? success.message : "Bot accepted the upload and started provisioning.";
4701
4737
  io.out(`✓ First-time deploy — provisioning workflow started for ${slug}`);
4702
4738
  io.out(` submission: ${submissionId}`);
4703
4739
  io.out(` appType: ${appType}`);
@@ -4709,12 +4745,12 @@ async function runModelADeploy(args) {
4709
4745
  io.out(` launchpad deploy # re-run once lifecycle is "live"`);
4710
4746
  return 0;
4711
4747
  }
4712
- if (typeof result.result.commit_sha !== "string" || typeof result.result.repo !== "string") {
4748
+ if (typeof success.commit_sha !== "string" || typeof success.repo !== "string") {
4713
4749
  io.err(`launchpad deploy: bot returned an unexpected 202 body — missing commit_sha or repo.`);
4714
- io.err(` got: ${JSON.stringify(result.result)}`);
4750
+ io.err(` got: ${JSON.stringify(success)}`);
4715
4751
  return 1;
4716
4752
  }
4717
- io.out(`✓ Bundle accepted — committed as ${result.result.commit_sha.slice(0, 8)} on ${result.result.repo}`);
4753
+ io.out(`✓ Bundle accepted — committed as ${success.commit_sha.slice(0, 8)} on ${success.repo}`);
4718
4754
  io.out(` ${result.fileCount} files (${formatBytes2(result.compressedBytes)} gzipped)`);
4719
4755
  if (result.workerScript !== null) {
4720
4756
  io.out(` cron Worker '${result.workerScript}' bundled + shipped (the bot deploys it after the commit)`);
@@ -4722,9 +4758,10 @@ async function runModelADeploy(args) {
4722
4758
  if (result.walk.skipped.length > 0) {
4723
4759
  io.out(` ${result.walk.skipped.length} entries skipped (.gitignore / default-ignore / symlink)`);
4724
4760
  }
4725
- if (typeof result.result.message === "string") {
4726
- io.out(` ${result.result.message}`);
4761
+ if (typeof success.message === "string") {
4762
+ io.out(` ${success.message}`);
4727
4763
  }
4764
+ surfaceDeployExtras(success, io, slug);
4728
4765
  io.out("");
4729
4766
  io.out("Committed; build pending — Cloudflare Pages is building this deploy now.");
4730
4767
  io.out(`Run \`launchpad status ${slug}\` to confirm the build outcome (success / failure + log excerpt).`);
@@ -7270,6 +7307,19 @@ async function fetchManifestStatus(cfg, slug, fetcher = fetch) {
7270
7307
  }
7271
7308
 
7272
7309
  // src/deploy/deployment-status.ts
7310
+ async function fetchStandingExceptions(cfg, slug, fetcher = fetch) {
7311
+ let raw;
7312
+ try {
7313
+ raw = await apiJson(cfg, { path: `/apps/${encodeURIComponent(slug)}/exceptions` }, fetcher);
7314
+ } catch (e) {
7315
+ if (e instanceof NotFoundError)
7316
+ return null;
7317
+ throw e;
7318
+ }
7319
+ if (!Array.isArray(raw.exceptions))
7320
+ return null;
7321
+ return raw.exceptions.filter((e) => typeof e === "object" && e !== null && typeof e.path === "string" && typeof e.rule === "string" && typeof e.detectedAt === "string" && typeof e.deployRef === "string");
7322
+ }
7273
7323
  async function fetchDeploymentStatus(cfg, slug, fetcher = fetch) {
7274
7324
  let raw;
7275
7325
  try {
@@ -7557,6 +7607,15 @@ async function runStatus(args, io) {
7557
7607
  }
7558
7608
  io.err(`launchpad status: live deployment state unavailable (${describe24(e)}) — ` + `the report below is from the platform manifest view only.`);
7559
7609
  }
7610
+ let standingExceptions = null;
7611
+ try {
7612
+ standingExceptions = await fetchStandingExceptions(cfg, parsed.slug);
7613
+ } catch (e) {
7614
+ if (e instanceof UnauthenticatedError || e instanceof ForbiddenError) {
7615
+ return mapBotError(e, parsed.slug, io);
7616
+ }
7617
+ io.err(`launchpad status: standing-exception inventory unavailable (${describe24(e)}).`);
7618
+ }
7560
7619
  if (state.manifestYaml === null || state.manifestYaml === undefined) {
7561
7620
  const live = deployment?.liveDeployment ?? null;
7562
7621
  const contentIsLive = live !== null && (live.buildStatus === "success" || deployment?.lastSuccessfulDeployment != null);
@@ -7570,7 +7629,8 @@ async function runStatus(args, io) {
7570
7629
  openPrNumber: state.openPr?.number ?? null,
7571
7630
  driftFields: [],
7572
7631
  driftDetails: [],
7573
- ...deploymentKnown ? { deployment } : {}
7632
+ ...deploymentKnown ? { deployment } : {},
7633
+ ...standingExceptions !== null ? { standingExceptions } : {}
7574
7634
  };
7575
7635
  emit3(out, parsed.json, io);
7576
7636
  return 0;
@@ -7602,7 +7662,8 @@ async function runStatus(args, io) {
7602
7662
  openPrNumber: state.openPr?.number ?? null,
7603
7663
  driftFields: drift.map((d) => d.path),
7604
7664
  driftDetails: drift,
7605
- ...deploymentKnown ? { deployment } : {}
7665
+ ...deploymentKnown ? { deployment } : {},
7666
+ ...standingExceptions !== null ? { standingExceptions } : {}
7606
7667
  };
7607
7668
  emit3(result, parsed.json, io);
7608
7669
  if (result.state === "drift" && parsed.strict) {
@@ -7689,21 +7750,25 @@ function emit3(out, asJson, io) {
7689
7750
  io.out(`${out.slug}: live — no content deployed yet. Run \`launchpad deploy\`.`);
7690
7751
  surfaceHeadVsDeployed(out, io);
7691
7752
  surfaceDeployment(out, io);
7753
+ surfaceExceptions(out, io);
7692
7754
  return;
7693
7755
  case "no_deployed_manifest":
7694
7756
  io.out(`${out.slug}: no deployed manifest yet — run \`launchpad deploy\`.`);
7695
7757
  surfaceHeadVsDeployed(out, io);
7696
7758
  surfaceDeployment(out, io);
7759
+ surfaceExceptions(out, io);
7697
7760
  return;
7698
7761
  case "live_content_untracked":
7699
7762
  io.out(`${out.slug}: live — content deployed via ` + `${triggerLabel(out.deployment?.liveDeployment?.trigger)} (no platform-tracked manifest; this app deploys outside \`launchpad deploy\`).`);
7700
7763
  surfaceHeadVsDeployed(out, io);
7701
7764
  surfaceDeployment(out, io);
7765
+ surfaceExceptions(out, io);
7702
7766
  return;
7703
7767
  case "in_sync":
7704
7768
  io.out(`${out.slug}: live, in sync` + (out.deployedSha ? ` (content @ ${out.deployedSha.slice(0, 7)})` : ""));
7705
7769
  surfaceHeadVsDeployed(out, io);
7706
7770
  surfaceDeployment(out, io);
7771
+ surfaceExceptions(out, io);
7707
7772
  return;
7708
7773
  case "drift":
7709
7774
  io.out(`${out.slug}: live, drift: ${out.driftFields.join(", ")}`);
@@ -7714,6 +7779,7 @@ function emit3(out, asJson, io) {
7714
7779
  }
7715
7780
  surfaceHeadVsDeployed(out, io);
7716
7781
  surfaceDeployment(out, io);
7782
+ surfaceExceptions(out, io);
7717
7783
  return;
7718
7784
  }
7719
7785
  }
@@ -7763,6 +7829,16 @@ function surfaceDeployment(out, io) {
7763
7829
  }
7764
7830
  }
7765
7831
  }
7832
+ function surfaceExceptions(out, io) {
7833
+ const exceptions = out.standingExceptions;
7834
+ if (exceptions === undefined || exceptions.length === 0)
7835
+ return;
7836
+ io.out(` standing exceptions: ${exceptions.length} policy violation(s) in content already live on main (non-blocking):`);
7837
+ for (const e of exceptions) {
7838
+ io.out(` - ${e.path} [${e.rule}]`);
7839
+ }
7840
+ io.out(" these never block a deploy that doesn't change them, but they are never grandfathered silently — clean them up in a future deploy.");
7841
+ }
7766
7842
  function surfaceHeadVsDeployed(out, io) {
7767
7843
  if (out.headSha !== null && out.deployedSha !== null && out.headSha !== out.deployedSha) {
7768
7844
  const short = (s) => s.slice(0, 7);
@@ -8160,6 +8236,7 @@ async function runRollback(opts, io, deps = {}) {
8160
8236
  file: opts.file,
8161
8237
  platformRepo: null,
8162
8238
  rePin: false,
8239
+ atSha: verifiedSha,
8163
8240
  yes: true
8164
8241
  }, io, applyDeps);
8165
8242
  }
@@ -54,6 +54,12 @@ export type DeployMode = {
54
54
  * so we use a distinct flag here.
55
55
  */
56
56
  readonly resumePrNumber: number | null;
57
+ /**
58
+ * `--at <sha>` (sp-devlp1 AC3) — pin the manifest to a specific
59
+ * MANAGED-repo sha. Default (null) lets the bot resolve managed
60
+ * main HEAD; local git state is irrelevant either way.
61
+ */
62
+ readonly atSha: string | null;
57
63
  /**
58
64
  * `--timeout-minutes <n>` — override the 90-minute default
59
65
  * polling timeout (AC-U2). Apply-only.
@@ -1 +1 @@
1
- {"version":3,"file":"deploy-flags.d.ts","sourceRoot":"","sources":["../../src/commands/deploy-flags.ts"],"names":[],"mappings":"AA0BA,OAAO,EAAa,KAAK,OAAO,EAAE,MAAM,aAAa,CAAC;AAEtD,MAAM,MAAM,UAAU,GAClB;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAC1D;IACE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3C,GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAClD;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE;AACrD;;;;;;;GAOG;GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE;AACpF;;;;;;;;;;GAUG;GACD;IACE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB;;;;;;OAMG;IACH,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC;;;OAGG;IACH,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CACxC,CAAC;AAEN,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,uDAAuD;IACvD,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CACxC;AAQD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,WAAW,GAAG,MAAM,CAsT9E"}
1
+ {"version":3,"file":"deploy-flags.d.ts","sourceRoot":"","sources":["../../src/commands/deploy-flags.ts"],"names":[],"mappings":"AA0BA,OAAO,EAAa,KAAK,OAAO,EAAE,MAAM,aAAa,CAAC;AAEtD,MAAM,MAAM,UAAU,GAClB;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAC1D;IACE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3C,GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAClD;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE;AACrD;;;;;;;GAOG;GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE;AACpF;;;;;;;;;;GAUG;GACD;IACE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB;;;;;;OAMG;IACH,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B;;;OAGG;IACH,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CACxC,CAAC;AAEN,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,uDAAuD;IACvD,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CACxC;AAQD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,WAAW,GAAG,MAAM,CAqU9E"}
@@ -1 +1 @@
1
- {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"AAgDA,OAAO,EAGL,4BAA4B,EAC7B,MAAM,uBAAuB,CAAC;AAM/B,OAAO,KAAK,EAAS,OAAO,EAAY,MAAM,kBAAkB,CAAC;AAEjE,eAAO,MAAM,aAAa,EAAE,OAI3B,CAAC;AAEF,UAAU,UAAU;IAClB,6BAA6B;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAuPD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,UAAU,GAAG,IAAI,CAwBpE;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI3D;AAiED,2BAA2B;AAC3B,OAAO,EAAE,4BAA4B,EAAE,CAAC;AAYxC;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAalE"}
1
+ {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"AAgDA,OAAO,EAGL,4BAA4B,EAC7B,MAAM,uBAAuB,CAAC;AAM/B,OAAO,KAAK,EAAS,OAAO,EAAY,MAAM,kBAAkB,CAAC;AAEjE,eAAO,MAAM,aAAa,EAAE,OAI3B,CAAC;AAEF,UAAU,UAAU;IAClB,6BAA6B;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAwPD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,UAAU,GAAG,IAAI,CAwBpE;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI3D;AA6GD,2BAA2B;AAC3B,OAAO,EAAE,4BAA4B,EAAE,CAAC;AAYxC;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAalE"}
@@ -1,4 +1,4 @@
1
- import { type DeploymentStatusView } from "../deploy/deployment-status.js";
1
+ import { type DeploymentStatusView, type StandingExceptionView } from "../deploy/deployment-status.js";
2
2
  import { type Manifest } from "@m-kopa/launchpad-engine";
3
3
  import type { Command } from "../dispatcher.js";
4
4
  export declare const statusCommand: Command;
@@ -42,6 +42,10 @@ export interface StatusJsonOutput {
42
42
  * Absent when the bot pre-dates the endpoint; null when fetched but
43
43
  * the app has no Pages surface. */
44
44
  readonly deployment?: DeploymentStatusView | null;
45
+ /** Standing-exception inventory (sp-devlp1 T3 / D2): non-blocking
46
+ * policy violations in content already live on managed main.
47
+ * Absent when the bot pre-dates the endpoint. */
48
+ readonly standingExceptions?: readonly StandingExceptionView[];
45
49
  }
46
50
  /**
47
51
  * Compute drift over the v1 closed field set (AC5).
@@ -1 +1 @@
1
- {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAyCA,OAAO,EAEL,KAAK,oBAAoB,EAC1B,MAAM,gCAAgC,CAAC;AASxC,OAAO,EAAqC,KAAK,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAC5F,OAAO,KAAK,EAAS,OAAO,EAAY,MAAM,kBAAkB,CAAC;AAEjE,eAAO,MAAM,aAAa,EAAE,OAI3B,CAAC;AAEF,UAAU,UAAU;IAClB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CAC1B;AAuBD;;;uEAGuE;AACvE,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,KAAK,EACV,cAAc,GACd,qBAAqB,GACrB,iBAAiB;IACnB;;wDAEoD;OAClD,wBAAwB,GACxB,SAAS,GACT,OAAO,GACP,sBAAsB,GACtB,YAAY,GACZ,WAAW,GACX,gBAAgB,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,0EAA0E;IAC1E,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,sDAAsD;IACtD,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,6DAA6D;IAC7D,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,oFAAoF;IACpF,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,4EAA4E;IAC5E,QAAQ,CAAC,YAAY,EAAE,aAAa,CAAC;QACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;QACxB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;KAC5B,CAAC,CAAC;IACH;;;wCAGoC;IACpC,QAAQ,CAAC,UAAU,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAC;CACnD;AAwND;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,QAAQ,EACf,QAAQ,EAAE,QAAQ,GACjB,aAAa,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CAAC,CA2DpE;AAmOD,2CAA2C;AAC3C,wBAAgB,SAAS,CACvB,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,GAAG,GAAE,MAAsB,GAC1B,UAAU,GAAG,MAAM,CAgErB"}
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAyCA,OAAO,EAGL,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC3B,MAAM,gCAAgC,CAAC;AASxC,OAAO,EAAqC,KAAK,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAC5F,OAAO,KAAK,EAAS,OAAO,EAAY,MAAM,kBAAkB,CAAC;AAEjE,eAAO,MAAM,aAAa,EAAE,OAI3B,CAAC;AAEF,UAAU,UAAU;IAClB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CAC1B;AAuBD;;;uEAGuE;AACvE,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,KAAK,EACV,cAAc,GACd,qBAAqB,GACrB,iBAAiB;IACnB;;wDAEoD;OAClD,wBAAwB,GACxB,SAAS,GACT,OAAO,GACP,sBAAsB,GACtB,YAAY,GACZ,WAAW,GACX,gBAAgB,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,0EAA0E;IAC1E,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,sDAAsD;IACtD,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,6DAA6D;IAC7D,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,oFAAoF;IACpF,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,4EAA4E;IAC5E,QAAQ,CAAC,YAAY,EAAE,aAAa,CAAC;QACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;QACxB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;KAC5B,CAAC,CAAC;IACH;;;wCAGoC;IACpC,QAAQ,CAAC,UAAU,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAClD;;sDAEkD;IAClD,QAAQ,CAAC,kBAAkB,CAAC,EAAE,SAAS,qBAAqB,EAAE,CAAC;CAChE;AAyOD;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,QAAQ,EACf,QAAQ,EAAE,QAAQ,GACjB,aAAa,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CAAC,CA2DpE;AA4PD,2CAA2C;AAC3C,wBAAgB,SAAS,CACvB,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,GAAG,GAAE,MAAsB,GAC1B,UAAU,GAAG,MAAM,CAgErB"}
@@ -9,6 +9,11 @@ export interface ApplyOptions {
9
9
  * Removal in v0.13 alongside `--platform-repo`. */
10
10
  readonly rePin: boolean;
11
11
  readonly yes: boolean;
12
+ /** `--at <sha>` — pin the manifest to a specific MANAGED-repo sha
13
+ * instead of letting the bot resolve managed main HEAD (sp-devlp1
14
+ * AC3). The sha must exist in the managed repo; a local-only sha
15
+ * is rejected by the bot with a pointer at the no-sha path. */
16
+ readonly atSha?: string;
12
17
  /** Re-attach to an in-flight apply for `--resume <prNumber>`. */
13
18
  readonly resumePrNumber?: number;
14
19
  /** Override the 90-minute default polling timeout (AC-U2). */
@@ -20,8 +25,6 @@ export interface ApplyDeps {
20
25
  readonly config?: CliConfig;
21
26
  /** Inject `fetch` for tests; defaults to `globalThis.fetch`. */
22
27
  readonly fetcher?: typeof fetch;
23
- /** Inject `git rev-parse` resolution for tests. */
24
- readonly gitHeadSha?: (cwd: string) => string;
25
28
  /** Polling interval seconds (defaults to 10s; tests use 0). */
26
29
  readonly pollIntervalSeconds?: number;
27
30
  }
@@ -1 +1 @@
1
- {"version":3,"file":"apply.d.ts","sourceRoot":"","sources":["../../src/deploy/apply.ts"],"names":[],"mappings":"AA8CA,OAAO,EAAc,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAU1D,OAAO,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAExD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B;iCAC6B;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC;wDACoD;IACpD,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,iEAAiE;IACjE,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,8DAA8D;IAC9D,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CAClC;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACxD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC;IAC5B,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC;IAC5B,gEAAgE;IAChE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;IAChC,mDAAmD;IACnD,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;IAC9C,+DAA+D;IAC/D,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;CACvC;AAwCD,wBAAsB,cAAc,CAClC,IAAI,EAAE,YAAY,EAClB,EAAE,EAAE,KAAK,EACT,IAAI,GAAE,SAAc,GACnB,OAAO,CAAC,QAAQ,CAAC,CAyLnB"}
1
+ {"version":3,"file":"apply.d.ts","sourceRoot":"","sources":["../../src/deploy/apply.ts"],"names":[],"mappings":"AAqDA,OAAO,EAAc,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAU1D,OAAO,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAExD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B;iCAC6B;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC;wDACoD;IACpD,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB;;;oEAGgE;IAChE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,iEAAiE;IACjE,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,8DAA8D;IAC9D,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CAClC;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACxD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC;IAC5B,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC;IAC5B,gEAAgE;IAChE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;IAChC,+DAA+D;IAC/D,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;CACvC;AAwCD,wBAAsB,cAAc,CAClC,IAAI,EAAE,YAAY,EAClB,EAAE,EAAE,KAAK,EACT,IAAI,GAAE,SAAc,GACnB,OAAO,CAAC,QAAQ,CAAC,CAoLnB"}
@@ -27,6 +27,24 @@ export interface DeploymentStatusView {
27
27
  readonly url: string | null;
28
28
  } | null;
29
29
  }
30
+ /** One standing-exception inventory entry (sp-devlp1 T3 / D2) — a
31
+ * policy/boundary/build-command violation observed in content already
32
+ * live on managed main. Non-blocking; recorded by the bot on every
33
+ * deploy that observes it. Mirror of the bot's `StandingException`. */
34
+ export interface StandingExceptionView {
35
+ readonly path: string;
36
+ readonly rule: string;
37
+ readonly detectedAt: string;
38
+ readonly deployRef: string;
39
+ }
40
+ /**
41
+ * Fetch the app's standing-exception inventory via
42
+ * `GET /apps/<slug>/exceptions` (owner/editor/break-glass). Returns
43
+ * `null` when the bot pre-dates the endpoint (404) or the response is
44
+ * shape-less — callers omit the block in that case. Auth errors
45
+ * propagate.
46
+ */
47
+ export declare function fetchStandingExceptions(cfg: CliConfig, slug: string, fetcher?: typeof fetch): Promise<readonly StandingExceptionView[] | null>;
30
48
  /**
31
49
  * Fetch the live deployment status. Returns `null` when the bot
32
50
  * pre-dates the endpoint (404) or the response doesn't carry the
@@ -1 +1 @@
1
- {"version":3,"file":"deployment-status.d.ts","sourceRoot":"","sources":["../../src/deploy/deployment-status.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,MAAM,MAAM,iBAAiB,GAAG,UAAU,GAAG,KAAK,GAAG,SAAS,CAAC;AAC/D,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,SAAS,GAAG,aAAa,CAAC;AAEhE,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAC;IACpC,QAAQ,CAAC,MAAM,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACtE,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAClC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8DAA8D;IAC9D,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,cAAc,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACnD,yEAAyE;IACzE,QAAQ,CAAC,wBAAwB,EAAE;QACjC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;QACpB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;QAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;KAC7B,GAAG,IAAI,CAAC;CACV;AAED;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CACzC,GAAG,EAAE,SAAS,EACd,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,OAAO,KAAa,GAC5B,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAuBtC"}
1
+ {"version":3,"file":"deployment-status.d.ts","sourceRoot":"","sources":["../../src/deploy/deployment-status.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,MAAM,MAAM,iBAAiB,GAAG,UAAU,GAAG,KAAK,GAAG,SAAS,CAAC;AAC/D,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,SAAS,GAAG,aAAa,CAAC;AAEhE,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAC;IACpC,QAAQ,CAAC,MAAM,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACtE,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAClC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8DAA8D;IAC9D,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,cAAc,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACnD,yEAAyE;IACzE,QAAQ,CAAC,wBAAwB,EAAE;QACjC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;QACpB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;QAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;KAC7B,GAAG,IAAI,CAAC;CACV;AAED;;;wEAGwE;AACxE,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;GAMG;AACH,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,SAAS,EACd,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,OAAO,KAAa,GAC5B,OAAO,CAAC,SAAS,qBAAqB,EAAE,GAAG,IAAI,CAAC,CAsBlD;AAED;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CACzC,GAAG,EAAE,SAAS,EACd,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,OAAO,KAAa,GAC5B,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAuBtC"}
@@ -1 +1 @@
1
- {"version":3,"file":"rollback.d.ts","sourceRoot":"","sources":["../../src/deploy/rollback.ts"],"names":[],"mappings":"AAkDA,OAAO,EAAkB,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5D,OAAO,EAAc,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAE7D,OAAO,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAIxD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,qDAAqD;IACrD,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,qDAAqD;IACrD,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC;IAChC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACxD;;;gDAG4C;IAC5C,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;CAChC;AAUD,wBAAsB,WAAW,CAC/B,IAAI,EAAE,eAAe,EACrB,EAAE,EAAE,KAAK,EACT,IAAI,GAAE,YAAiB,GACtB,OAAO,CAAC,QAAQ,CAAC,CAmInB"}
1
+ {"version":3,"file":"rollback.d.ts","sourceRoot":"","sources":["../../src/deploy/rollback.ts"],"names":[],"mappings":"AAkDA,OAAO,EAAkB,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5D,OAAO,EAAc,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAE7D,OAAO,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAIxD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,qDAAqD;IACrD,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,qDAAqD;IACrD,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC;IAChC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACxD;;;gDAG4C;IAC5C,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;CAChC;AAUD,wBAAsB,WAAW,CAC/B,IAAI,EAAE,eAAe,EACrB,EAAE,EAAE,KAAK,EACT,IAAI,GAAE,YAAiB,GACtB,OAAO,CAAC,QAAQ,CAAC,CAyInB"}
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const CLI_VERSION = "0.24.0";
1
+ export declare const CLI_VERSION = "0.25.0";
2
2
  //# sourceMappingURL=version.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@m-kopa/launchpad-cli",
3
- "version": "0.24.0",
3
+ "version": "0.25.0",
4
4
  "description": "Launchpad CLI — clone / deploy / review / merge against Launchpad-managed apps. Talks to the portal-bot endpoints (SCOPE-M-760 / T4).",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: launchpad-content-pr
3
3
  description: Push a content change to a Launchpad app via `launchpad deploy` and verify it shipped via `launchpad status`. Covers the post-first-deploy iteration loop (edit → deploy → verify) and the stack-fit pre-flight that the bot enforces server-side. Use when someone says "push a content change", "ship an update", "/launchpad-content-pr", "verify my deploy", or after `/launchpad-deploy` reports `done` and they want to follow up with an edit.
4
- version: 0.24.0
4
+ version: 0.25.0
5
5
  ---
6
6
 
7
7
  <!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: launchpad-deploy
3
3
  description: Walk a Launchpad user through deploying an app from their local working directory (Model A — `launchpad init` + `launchpad deploy`). Wraps the CLI verbs end-to-end: detects the app shape, scaffolds `launchpad.yaml`, resolves the allowed Entra group via `launchpad groups`, bundles the CWD via `launchpad deploy`, and tails the resulting content PR. Use when someone says "deploy a new app", "ship my app to Launchpad", "/launchpad-deploy", "I have an app locally — get it on Launchpad", or any variant. Resume/abandon for legacy in-flight provisioning is at the bottom.
4
- version: 0.24.0
4
+ version: 0.25.0
5
5
  ---
6
6
 
7
7
  <!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: launchpad-deploy-status
3
3
  description: Show the current provisioning stage + failure reason for a Launchpad app via `launchpad status` (Model A drift + deployment_verified) and `launchpad apps` (lifecycle bucket). Renders the M-892 stage trace for legacy in-flight provisioning. Use when someone says "what's the status of demo-X", "/launchpad-deploy-status", "is my deploy stuck", or after `/launchpad-deploy` reports a non-`done` terminal stage.
4
- version: 0.24.0
4
+ version: 0.25.0
5
5
  ---
6
6
 
7
7
  <!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: launchpad-destroy
3
3
  description: Tear down a Launchpad app end-to-end via `launchpad destroy` — Cloudflare Pages project, Access app, custom hostname, platform-repo TF entries, and the app repo (archive-renamed). Owner-only verb with a two-step destructive confirmation. Use when someone says "destroy this app", "/launchpad-destroy", "tear down `<slug>`", "delete the app", or asks to clean up a smoke-test / orphan / retired app.
4
- version: 0.24.0
4
+ version: 0.25.0
5
5
  ---
6
6
 
7
7
  <!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: launchpad-onboard
3
3
  description: One-time setup for the Launchpad CLI + Claude Code skill bundle. Verifies the `launchpad` CLI is installed and current, runs `launchpad whoami` to confirm the Cf Access session is fresh, and checks the bundled skills are installed and in lock-step with the CLI. Idempotent — safe to re-run any time. Use when someone says "set me up for Launchpad", "I just got a new machine and want to use Launchpad", "/launchpad-onboard", or any of the other launchpad-* skills fails on a prereq check.
4
- version: 0.24.0
4
+ version: 0.25.0
5
5
  ---
6
6
 
7
7
  <!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
@@ -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.24.0
4
+ version: 0.25.0
5
5
  ---
6
6
 
7
7
  <!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->