@msn-control/liftoff 0.7.0 → 0.9.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 (114) hide show
  1. package/README.md +13 -1
  2. package/assets/governance/single-maintainer-gitflow/policy.md +705 -0
  3. package/assets/locks/frontend/package-lock.json +1012 -1694
  4. package/assets/locks/frontend/package.json +5 -6
  5. package/assets/locks/go-backend/go.mod +17 -0
  6. package/assets/locks/go-backend/go.sum +159 -0
  7. package/assets/locks/node-backend/package-lock.json +1473 -2320
  8. package/assets/locks/node-backend/package.json +8 -7
  9. package/assets/locks/opentofu-azure/.terraform.lock.hcl +39 -0
  10. package/assets/locks/opentofu-azure/versions.tf +10 -0
  11. package/assets/locks/python-genai/function-requirements.txt +1734 -0
  12. package/assets/locks/python-genai/liftoff_template_python_genai.egg-info/PKG-INFO +25 -0
  13. package/assets/locks/python-genai/liftoff_template_python_genai.egg-info/SOURCES.txt +6 -0
  14. package/assets/locks/python-genai/liftoff_template_python_genai.egg-info/dependency_links.txt +1 -0
  15. package/assets/locks/python-genai/liftoff_template_python_genai.egg-info/requires.txt +23 -0
  16. package/assets/locks/python-genai/liftoff_template_python_genai.egg-info/top_level.txt +1 -0
  17. package/assets/locks/python-genai/pyproject.toml +42 -0
  18. package/assets/locks/python-genai/uv.lock +2536 -0
  19. package/assets/locks/python-standard/pyproject.toml +36 -0
  20. package/assets/locks/python-standard/uv.lock +1780 -0
  21. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/catalog.json +4 -4
  22. package/assets/power-apps-code-app/3438c352483e40982f6c5c0fc36fd71f8e7adbbb/starter/package-lock.json +667 -1108
  23. package/assets/supported-stack.json +711 -0
  24. package/dist/args.js +14 -0
  25. package/dist/args.js.map +1 -1
  26. package/dist/catalogs.d.ts +3 -1
  27. package/dist/catalogs.js +29 -7
  28. package/dist/catalogs.js.map +1 -1
  29. package/dist/cli.js +2 -1
  30. package/dist/cli.js.map +1 -1
  31. package/dist/commands.d.ts +6 -0
  32. package/dist/commands.js +331 -41
  33. package/dist/commands.js.map +1 -1
  34. package/dist/container-validation.d.ts +2 -0
  35. package/dist/container-validation.js +26 -0
  36. package/dist/container-validation.js.map +1 -0
  37. package/dist/file-system.js +130 -5
  38. package/dist/file-system.js.map +1 -1
  39. package/dist/framework-adapters.d.ts +1 -1
  40. package/dist/framework-adapters.js +10 -3
  41. package/dist/framework-adapters.js.map +1 -1
  42. package/dist/framework-validation.d.ts +1 -0
  43. package/dist/framework-validation.js +26 -0
  44. package/dist/framework-validation.js.map +1 -1
  45. package/dist/go-template-assets.d.ts +2 -0
  46. package/dist/go-template-assets.js +14 -0
  47. package/dist/go-template-assets.js.map +1 -0
  48. package/dist/init-filesystem.js +5 -4
  49. package/dist/init-filesystem.js.map +1 -1
  50. package/dist/interactive.d.ts +13 -0
  51. package/dist/interactive.js +25 -7
  52. package/dist/interactive.js.map +1 -1
  53. package/dist/openspec-profile.d.ts +28 -0
  54. package/dist/openspec-profile.js +172 -0
  55. package/dist/openspec-profile.js.map +1 -0
  56. package/dist/opentofu-template-assets.d.ts +2 -0
  57. package/dist/opentofu-template-assets.js +10 -0
  58. package/dist/opentofu-template-assets.js.map +1 -0
  59. package/dist/package-identity.d.ts +8 -0
  60. package/dist/package-identity.js +16 -0
  61. package/dist/package-identity.js.map +1 -0
  62. package/dist/planner.js +61 -4
  63. package/dist/planner.js.map +1 -1
  64. package/dist/power-apps-templates.js +18 -0
  65. package/dist/power-apps-templates.js.map +1 -1
  66. package/dist/project-dependencies.d.ts +1 -1
  67. package/dist/project-dependencies.js +15 -34
  68. package/dist/project-dependencies.js.map +1 -1
  69. package/dist/published-verifier.js +11 -1
  70. package/dist/published-verifier.js.map +1 -1
  71. package/dist/python-template-assets.d.ts +4 -0
  72. package/dist/python-template-assets.js +34 -0
  73. package/dist/python-template-assets.js.map +1 -0
  74. package/dist/repository-governance.d.ts +17 -0
  75. package/dist/repository-governance.js +507 -0
  76. package/dist/repository-governance.js.map +1 -0
  77. package/dist/runtime.d.ts +1 -1
  78. package/dist/runtime.js +3 -1
  79. package/dist/runtime.js.map +1 -1
  80. package/dist/self-upgrade.d.ts +77 -0
  81. package/dist/self-upgrade.js +499 -0
  82. package/dist/self-upgrade.js.map +1 -0
  83. package/dist/stable-release.d.ts +18 -0
  84. package/dist/stable-release.js +66 -0
  85. package/dist/stable-release.js.map +1 -0
  86. package/dist/standard-templates.js +28 -97
  87. package/dist/standard-templates.js.map +1 -1
  88. package/dist/supported-stack.d.ts +115 -0
  89. package/dist/supported-stack.js +439 -0
  90. package/dist/supported-stack.js.map +1 -0
  91. package/dist/telemetry/contract.d.ts +1 -1
  92. package/dist/telemetry/contract.js +1 -0
  93. package/dist/telemetry/contract.js.map +1 -1
  94. package/dist/templates.js +238 -127
  95. package/dist/templates.js.map +1 -1
  96. package/dist/types.d.ts +21 -1
  97. package/dist/workstation-catalog.js +15 -12
  98. package/dist/workstation-catalog.js.map +1 -1
  99. package/dist/workstation.js +16 -6
  100. package/dist/workstation.js.map +1 -1
  101. package/docs/cli-reference.md +72 -2
  102. package/docs/configuration-and-manifests.md +13 -5
  103. package/docs/existing-repositories.md +44 -8
  104. package/docs/getting-started.md +34 -9
  105. package/docs/prerequisites.md +47 -4
  106. package/docs/project-structure.md +27 -1
  107. package/docs/repository-governance.md +91 -0
  108. package/docs/safety-and-consent.md +50 -13
  109. package/docs/spec-workflows-and-agents.md +61 -9
  110. package/docs/supported-stack.md +89 -0
  111. package/docs/telemetry.md +6 -0
  112. package/docs/troubleshooting.md +115 -0
  113. package/docs/workloads.md +14 -5
  114. package/package.json +14 -6
@@ -0,0 +1,705 @@
1
+ ---
2
+ schemaVersion: 1
3
+ profile: single-maintainer-gitflow
4
+ policyVersion: "1"
5
+ state: handoff-generated
6
+ ---
7
+
8
+ # Repository bootstrap standard — GitFlow, governance, and security
9
+
10
+ Set up GitFlow branching, repository governance, and the security pipeline in this repository.
11
+ This is my standard for every new repository, so implement it as a repeatable baseline rather than a
12
+ one-off.
13
+
14
+ ## Fixed context — these are settled, do not re-litigate them
15
+
16
+ - **Single-maintainer repositories, by design.** Each developer owns their own repository and is its
17
+ sole maintainer. There is no second reviewer, and none is wanted. **No change in this repository
18
+ requires another person's approval — including changes to workflows, rulesets and governance files.**
19
+ The repository owner merges their own pull requests. Automated checks are the entire gate, so they
20
+ must be strict and must fail closed. Never design anything that depends on someone else reviewing it,
21
+ and never treat the absence of peer review as a gap to be closed.
22
+ - **Do not require human approval anywhere.** Specifically: set
23
+ `required_approving_review_count: 0`, `require_code_owner_review: false` and
24
+ `require_last_push_approval: false`. Do not create a `CODEOWNERS` file. Do not add required
25
+ reviewers to any GitHub Environment. Do not add a manual approval step to any workflow. If a
26
+ best-practice default would introduce a human approver, override it and say so.
27
+ To be precise: me approving *your plan* in this conversation is expected and required. What is
28
+ forbidden is any *merge or deploy gate that waits on a person* once the automation is in place.
29
+ - **Repository-scoped only. Org-level rulesets are out of scope and will not be set up.** I am a
30
+ repository admin, not an org owner. Every control must be applied per-repository and must work
31
+ standalone in a single repository. Do not propose, recommend, or design around org-level rulesets,
32
+ org-level required workflows, or org-wide GitHub App installations — not even as a "better
33
+ alternative" or a future phase. Do not treat the per-repository approach as a limitation to be
34
+ worked around; it is the deliberate design. If a control genuinely cannot be enforced at repository
35
+ scope, say so plainly and leave it out rather than proposing an org-level substitute.
36
+ - **Automation identity is the built-in `GITHUB_TOKEN`.** Set the **GitHub Actions app** as the bypass
37
+ actor where automation must act on a protected ref — no custom GitHub App is available, since
38
+ installing one requires org ownership. Note that pushes made with `GITHUB_TOKEN` do not trigger
39
+ further workflows: never rely on a tag-push trigger, and do the follow-on work in the same workflow
40
+ run.
41
+ - **GitHub Advanced Security is licensed org-wide.** Use Secret Protection, CodeQL and Copilot Autofix.
42
+ Do not add third-party equivalents such as Gitleaks or Semgrep — they would duplicate licensed
43
+ capability.
44
+ - **Staging sits behind private networking.** DAST must run on a self-hosted runner with access to it.
45
+ - **The GitHub Release plus attestations are the audit record.** There is no external GRC or
46
+ change-management system to integrate with.
47
+
48
+ ## Basis
49
+
50
+ The branching model is Vincent Driessen's original GitFlow:
51
+ https://nvie.com/posts/a-successful-git-branching-model/
52
+ Follow its branch roles and merge directions faithfully. Note Driessen's own 2020 addendum — GitFlow
53
+ suits versioned releases rather than continuous delivery. If this repository genuinely ships
54
+ continuously, say so and tell me where you are deviating and why, rather than forcing the full model.
55
+
56
+ ## Phase 0 — Classify the repository first
57
+
58
+ Report before you change anything.
59
+
60
+ 1. What kind of artifact does this repo produce — container image, mobile app, library, static site,
61
+ infrastructure only? This determines which parts of the standard apply.
62
+ 2. Language(s), package managers, and the build and test commands that genuinely work today.
63
+ 3. Anything already present: branches, workflows and their exact job names, rulesets, tags, releases,
64
+ environments, deployment pipelines, security scanning.
65
+ 4. Whether a self-hosted runner group with Staging access exists. If it does not, say so plainly —
66
+ DAST cannot run without it, and the release lane will be unable to qualify a candidate.
67
+ 5. What monitoring and alerting already exists — alert rules, action groups, where they route, and
68
+ which components have no coverage at all. Name the gaps explicitly.
69
+ 6. Which components expose a health endpoint, and whether it is shallow (process is alive) or deep
70
+ (dependencies are reachable). A shallow check reported as health is a gap, not coverage.
71
+
72
+ Then state the gap and your proposed order of work, and **get my approval before making changes.**
73
+
74
+ **Adapt honestly.** Container scanning is meaningless for a mobile app; SBOM and image digests do not
75
+ apply to a library the same way. Implement what is real for this repo and tell me explicitly what you
76
+ skipped and why. Never ship a workflow that cannot pass.
77
+
78
+ ## Phase 1 — Branching model
79
+
80
+ - `develop` is the integration branch and the **default branch**. Feature branches (`feat/`, `fix/`,
81
+ `chore/`, `ci/`, `docs/`) branch from `develop` and merge back into `develop` by pull request only.
82
+ - `main` is production truth. Every commit on it is a released version. It only ever receives merges
83
+ from `release/**` or `hotfix/**`.
84
+ - `release/X.Y.Z` branches from `develop` — cut only by `workflow_dispatch` from an exact named healthy
85
+ SHA, never from a local checkout. Only stabilisation fixes land on it. On completion it merges to
86
+ `main` and back into `develop` so fixes are never lost.
87
+ - `hotfix/X.Y.Z` branches from `main`, requires an incident reference, and merges to both `main` and
88
+ `develop` — or into the open release branch if one exists.
89
+ - Nobody pushes directly to any protected branch, including me.
90
+
91
+ ## Phase 2 — Release versioning on `main`
92
+
93
+ Every production merge must produce a real, visible version.
94
+
95
+ - Semantic versioning. The `release/X.Y.Z` or `hotfix/X.Y.Z` branch name is the single source of truth
96
+ for the version; nothing else declares it.
97
+ - Merges into `main` are true merge commits, never squashed, so both parents stay traceable.
98
+ - After a successful production deploy — and only then — automation creates an annotated `vX.Y.Z` tag
99
+ on that exact `main` merge commit and a matching **GitHub Release** targeting `main`.
100
+ - The Release body must contain: the changelog between the previous tag and this one, the deployed
101
+ artifact digest, the source `develop` SHA the release was cut from, a link to the staging
102
+ qualification run, the **evidence bundle digest** and links to the SBOM and scan reports, the
103
+ **expected signer identity and OIDC issuer** an auditor should pin when verifying, and an AI
104
+ Acceptable Use Policy attestation record. For a hotfix, include the incident reference. Attach the
105
+ **SBOM, the full evidence bundle, the attestation bundle and `trusted_root.jsonl`** as Release
106
+ assets — these are the durable audit record and must not live only in expiring workflow artifacts.
107
+ Publishing the trusted root alongside the bundle is what makes offline, air-gapped verification
108
+ possible; without it an auditor with no network access to Sigstore cannot verify anything.
109
+ - Before tagging, validate: the version is valid semver, is strictly greater than the latest tag, does
110
+ not already exist as a tag or Release, and the commit is on `main` and actually deployed. Fail rather
111
+ than tag speculatively.
112
+ - Only automation creates `v*` tags. Tags are immutable — never deleted, moved or force-updated. A
113
+ mistaken release is corrected by publishing the next patch version.
114
+ - A failed production deploy creates no tag and no Release. That version is burnt; the repair path uses
115
+ the next patch version. Never reuse a burnt version.
116
+ - Any commit on `main` without a corresponding `vX.Y.Z` tag is an anomaly — add a check that reports it.
117
+
118
+ ## Phase 3 — Promotion: build once, promote the identical artifact
119
+
120
+ The artifact is built once per candidate and recorded in a release manifest with its digest, SBOM
121
+ digest, provenance attestation and scan results. Dev, Staging and Production all deploy that same
122
+ digest. Never rebuild per environment; never resolve a floating tag like `latest`. Configuration
123
+ differs per environment; the artifact does not.
124
+
125
+ - **Dev** — every push to `develop` deploys automatically. No gate.
126
+ - **Staging** — a push to `release/**` or `hotfix/**` builds the candidate, scans it, generates its
127
+ SBOM, deploys to Staging, then runs the qualification suite and DAST. It records a qualification
128
+ record bound to that exact digest and commit.
129
+ - **Production** — merging into `main` promotes. The workflow resolves the already-qualified digest,
130
+ verifies the attestation and qualification record bind to that exact commit and digest, and refuses
131
+ to proceed on any mismatch. Deployment is recorded through a GitHub Environment for the deployment
132
+ history and audit trail only — configure it with **no required reviewers**, so promotion is never
133
+ blocked waiting on a person.
134
+ - Promotion is strictly forward. Rollback redeploys a previous release manifest **by digest**, never a
135
+ rebuild, and records which version it rolled back to. Rollback is never gated — see "Roll back
136
+ first, debug later" below.
137
+
138
+ ### Deployment strategy — blue-green mechanism, canary exposure
139
+
140
+ Two separate decisions, often conflated. Be explicit about both.
141
+
142
+ **1. Mechanism — how a version replaces another within one deployment unit. Always blue-green.**
143
+
144
+ - Run the platform in a mode that supports parallel versions (for Azure Container Apps, `Multiple`
145
+ revision mode; for App Service, deployment slots; for Kubernetes, a parallel deployment behind a
146
+ switchable service).
147
+ - Deploy the new version alongside the current one at **zero traffic**.
148
+ - Qualify it on a **version-specific origin** — its own revision URL or slot hostname — while it still
149
+ serves no users. This is the step that makes the model work: verification happens before any user is
150
+ exposed, not after.
151
+ - Only then switch traffic **atomically** to 100% of the new version, setting all prior versions to 0.
152
+ - Run post-switch smoke checks. On failure, **restore the previous traffic weights** and set the
153
+ failed version to 0. Rollback is a traffic change measured in seconds, not a redeploy.
154
+ - Never leave a deployment unit in a mixed-version state. Two versions may exist simultaneously, but
155
+ only one serves traffic. That guarantee is what lets an auditor ask "what was running at 14:32" and
156
+ get exactly one answer — the attestation and evidence chain depends on it.
157
+ - Do **not** use rolling in-place updates. They create a mixed-version window, make rollback slow, and
158
+ break that guarantee.
159
+
160
+ **2. Exposure — how much traffic sees the new version, and when. Canary before full traffic.**
161
+
162
+ The Dev → Staging → Production path is already progressive exposure. Canary extends it *inside*
163
+ production so that a defect which survived Staging is caught by a small fraction of real users rather
164
+ than all of them.
165
+
166
+ Canary runs **on top of** the blue-green mechanism above — they are complementary, not alternatives.
167
+ Blue-green is how a version gets deployed and rolled back; canary is how traffic reaches it.
168
+
169
+ **The canary sequence:**
170
+
171
+ 1. Deploy the new version at **zero traffic** and qualify it on its version-specific origin, exactly
172
+ as above. Nothing reaches a user until this passes.
173
+ 2. Deploy a **fresh baseline revision of the current version** alongside it. Compare the canary
174
+ against this baseline, **never against the existing production fleet** — the running fleet has
175
+ warmed caches, longer uptime and settled runtime state, which makes it an invalid control and
176
+ produces false signals in both directions. This is the detail that makes canary analysis
177
+ trustworthy, and the one most often got wrong.
178
+ 3. **Experiment phase.** Shift a small slice of traffic to the canary and an **equal slice to the
179
+ fresh baseline** — for example 10% canary, 10% baseline, with the remaining 80% still served by the
180
+ current fleet. Equal slices are what make the comparison fair.
181
+ 4. Hold for a defined **bake window** and run automated analysis over it.
182
+ 5. **Promotion phase.** On a passing analysis, switch **atomically to 100% canary** and retire both
183
+ the baseline and the old revision.
184
+
185
+ **Do not ramp the canary and baseline through intermediate steps such as 50/50.** Two reasons. First,
186
+ traffic weights sum to 100, so canary 50 plus baseline 50 leaves the original revision at zero — that
187
+ moves every user onto two freshly deployed, cold revisions and abandons the warm, proven fleet you
188
+ would otherwise fall back to. Second, the baseline is a *control*, not a rollout vehicle; its only job
189
+ is to match the canary's slice size. The comparison is either statistically valid at the experiment
190
+ slice or it is not, and a larger slice does not rescue an invalid one — it only exposes more users.
191
+ Go from the experiment slice straight to the atomic switch.
192
+
193
+ **Analysis and gating:**
194
+
195
+ - Compare error rate, latency (p50, p95, p99) and saturation between canary and baseline over the
196
+ bake window, against explicit, committed thresholds.
197
+ - **Advancement must be fully automated.** There is no reviewer on these repositories, so a canary
198
+ that waits for a human to click "promote" is not a control — it is a pause. Encode the decision.
199
+ - **Fail closed.** If metrics are missing, the analysis cannot reach a conclusion, or the bake window
200
+ times out, **roll back** — never advance on absent evidence. A canary that proceeds when it cannot
201
+ measure anything is the fail-open pattern in a different costume.
202
+ - Any threshold breach triggers an **immediate rollback to 100% previous version** — restore the
203
+ original revision's weight and set canary and baseline to 0.
204
+ - Record the canary analysis result — thresholds, observed values, decision — in the evidence bundle
205
+ for that release.
206
+
207
+ **Size the canary to the traffic that actually exists.** The bake window must be long enough for the
208
+ slice to produce a meaningful sample; a five-minute window on a service with a handful of requests
209
+ proves nothing and merely delays the release. State the assumed traffic volume and the resulting bake
210
+ window explicitly, and if the service is too quiet for a canary slice to be meaningful, say so and use
211
+ a single atomic switch with instant rollback instead — but say it, rather than shipping a canary that
212
+ is statistically empty.
213
+
214
+ Where more than one region or deployment unit exists, prefer making the **first unit** the canary
215
+ (Microsoft's Azure Safe Deployment Practices model — Canary → Pilot → Broad, each a region with its
216
+ own bake time) over splitting traffic within a single unit. A failure is then contained to one unit,
217
+ and rollback is a traffic switch you have already tested.
218
+
219
+ ### Roll back first, debug later
220
+
221
+ This is the single most valuable deployment practice at any scale, and it is policy here, not advice.
222
+
223
+ - When canary analysis fails or post-switch checks fail, **restore traffic to the previous version
224
+ immediately.** Do not investigate first. Do not "just check one thing." The rollback is a
225
+ traffic-weight change measured in seconds — it costs almost nothing, and the exposure window while
226
+ you diagnose costs users.
227
+ - **Retain the failed revision at zero traffic** for forensics. It serves nobody but remains available
228
+ for inspection, along with logs and telemetry captured during the exposure.
229
+ - **Never forward-fix under pressure.** The next patch version, through the normal gated path, is the
230
+ only route back. A hotfix that skips the gates to resolve an incident faster is how an incident
231
+ becomes two incidents.
232
+ - **Rollback must never be gated.** It requires no approval, no qualification record, and no ruleset
233
+ bypass — it redeploys an artifact that already passed every gate. Verify that the rollback path
234
+ genuinely works, and rehearse it, before requiring anything that depends on it.
235
+ - Automated rollback is the default path; a manual rollback workflow exists as a backstop and takes an
236
+ incident reference.
237
+
238
+ If the target platform cannot support parallel versions, say so plainly and describe what you
239
+ implemented instead — do not silently fall back to an in-place update and call it a deployment.
240
+
241
+ ### Runtime monitoring and alerting
242
+
243
+ The security pipeline proves the artifact was sound when it shipped. Monitoring is what tells you the
244
+ running system is sound now. Both are required; neither substitutes for the other.
245
+
246
+ **Alerting is infrastructure as code.** Define every alert rule, action group and routing target in
247
+ the same IaC as the resources they watch, reviewed and versioned alongside them. Never configure an
248
+ alert by hand in a portal — a hand-made alert is invisible to review, absent from a rebuilt
249
+ environment, and lost when a resource is recreated.
250
+
251
+ **Route everything to Slack, and route by severity.** Use a single action group per environment with
252
+ a Slack webhook receiver, and separate channels by severity so that noise and urgency do not share a
253
+ destination:
254
+
255
+ - **Sev 1 — service down or failing.** Sustained 5xx rate, container restart loop, health probe
256
+ failing, database unreachable. Goes to an alerting channel that is expected to interrupt someone.
257
+ - **Sev 2 — degraded or trending toward failure.** Saturation (CPU, memory, storage, connection
258
+ pool), elevated latency, queue depth growing, certificate or secret expiring within a defined
259
+ window. Goes to a lower-urgency channel.
260
+ - **Sev 3 — informational.** Deployment started and finished, canary promoted or rolled back,
261
+ scheduled re-scan found a new CVE in a released artifact. Goes to a log channel — useful context,
262
+ never an interruption.
263
+
264
+ Include environment, resource, severity, the firing condition with its observed value, and a direct
265
+ link to the resource and to the relevant dashboard in every message. An alert that says only
266
+ "something is wrong" costs more time than it saves.
267
+
268
+ **Cover every component that can fail independently**, not just the application:
269
+
270
+ - **Web / frontend** — availability from outside the network, 5xx rate, and a synthetic check of the
271
+ real user-facing hostname rather than an internal origin.
272
+ - **API / backend** — 5xx rate, p95 latency, restart count, health and readiness endpoint failures.
273
+ - **Database** — availability, storage percentage, CPU, connection saturation, replication lag,
274
+ and **backup success**. A failed backup is silent until the day it matters.
275
+ - **Cache / session store** — memory saturation and error rate. Where sessions or sign-in depend on
276
+ it, treat its failure as Sev 1: cache availability *is* authentication availability.
277
+ - **Messaging and communication services** (managed email/SMS/push services such as Azure
278
+ Communication Services, and message brokers such as Service Bus, Event Hubs, SQS or Kafka) —
279
+ delivery failure rate, queue depth, dead-letter count, and throttling. These fail quietly and are
280
+ usually discovered by a user reporting a message that never arrived, which is far too late.
281
+ - **Ingress / CDN / gateway** — origin health, TLS certificate expiry, and 4xx/5xx at the edge, which
282
+ catches failures that never reach the application at all.
283
+ - **Identity and secrets** — secret, certificate and credential expiry, with enough lead time to act.
284
+ - **Cost** — a budget threshold alert. A runaway cost is an incident, and it is often the first
285
+ visible symptom of a runaway process.
286
+
287
+ **Alert on symptoms, not causes.** Alert on what a user would notice — requests failing, requests
288
+ slow, messages not delivered. Resource-level signals belong to dashboards and to Sev 2 at most. A page
289
+ per underlying cause produces a flood during a single incident.
290
+
291
+ **Every alert must be actionable.** If nobody would do anything when it fires, it is a dashboard
292
+ metric, not an alert. Thresholds should be deliberately loose enough that a normal day is quiet: an
293
+ alert that fires routinely gets muted, and a muted alert is worse than no alert because it looks like
294
+ coverage while providing none.
295
+
296
+ **Alerting must not fail open** — the same defect class as a fail-open CI check, and considerably
297
+ harder to notice, because an environment with no alerts looks exactly like an environment with no
298
+ problems:
299
+
300
+ - Never make alert creation conditional on an optional variable being set. A pattern such as
301
+ `count = var.alert_webhook != "" ? 1 : 0` means a missing value silently produces an environment
302
+ with no alerts at all. Make the routing target **required**, and fail the deployment if it is
303
+ absent.
304
+ - Verify after deploy that the expected alert rules exist and are enabled, and fail the deployment if
305
+ any are missing. Treat a missing alert rule as a failed deployment.
306
+ - **Add a heartbeat.** Emit a scheduled signal that proves the whole path — metric to rule to action
307
+ group to Slack — is alive, and alert on its *absence*. Without one, a broken webhook is
308
+ indistinguishable from a healthy system.
309
+ - **Test that each alert fires**, exactly as required checks must be proven to go red. An alert that
310
+ has only ever been seen quiet has never been shown to work. Record the test.
311
+
312
+ **Wire alerting into the deployment path.** The canary analysis in the previous section reads the same
313
+ signals: use one definition of healthy so that the thresholds gating a canary and the thresholds
314
+ paging a human cannot drift apart. Announce deployment start, canary decision and rollback to Slack —
315
+ correlating "it broke" with "we shipped" is the single most useful piece of incident context.
316
+ Alert noise should be suppressed for the deploying resource during an expected restart, but never
317
+ suppressed globally.
318
+
319
+ Feed Defender for Cloud and Microsoft Sentinel where configured, and route their high-severity
320
+ findings to the same Slack destinations, so security and availability alerts reach one place rather
321
+ than two.
322
+
323
+ #### Service health model — know the state of everything, to recover quickly
324
+
325
+ Alerting tells you something broke. A health model tells you **what state every component is in right
326
+ now**, which is what recovery actually requires. Build both.
327
+
328
+ - Give **every component a machine-readable health state** — the web frontend, each API, the
329
+ database, the cache, messaging and communication services, storage, ingress, and every managed
330
+ cloud dependency. Not just "the app is up".
331
+ - Distinguish **shallow from deep checks**. A liveness endpoint that returns 200 from the process
332
+ proves the process is running. A readiness or deep check must exercise the real dependency path —
333
+ a database round trip, a cache read, a token acquisition — because "the API is up but cannot reach
334
+ the database" is the state you most need to see, and the one a shallow check hides.
335
+ - **Aggregate into one view** showing, per environment, every component and its state, the deployed
336
+ version and artifact digest, and when it was last checked. During an incident the first question is
337
+ always "what is broken and what is fine", and answering it by opening six consoles costs the
338
+ minutes that matter.
339
+ - **Record dependencies** so the view shows blast radius and recovery order. If the database is down,
340
+ the APIs depending on it are *consequences*, not separate incidents. Without this, one failure
341
+ presents as ten alerts and the actual cause is guesswork.
342
+ - Report each component's **currently deployed version and digest** alongside its health. Recovery
343
+ decisions turn on whether a component is running what you think it is running, and this is also
344
+ what makes "roll back first" actionable.
345
+ - The health view must be **queryable when things are broken**. Do not host it inside the system it
346
+ monitors, and do not let it depend on that system's database or identity provider. A status page
347
+ that goes down with the service is worse than none, because its silence is ambiguous.
348
+ - Expose it as **structured data, not only a dashboard**, so the deployment pipeline, the canary
349
+ analysis and the alert rules can all consume the same health definition rather than each
350
+ maintaining a private one.
351
+ - Also surface **cloud provider platform status** for the regions and services you depend on. A
352
+ provider-side incident needs a different response from a defect you shipped, and telling them apart
353
+ early prevents a pointless rollback.
354
+
355
+ #### Delivery performance — the DORA four keys
356
+
357
+ Measure delivery performance using Google's DORA metrics, and derive them from the events this
358
+ pipeline already emits rather than from a separate system:
359
+
360
+ | Metric | Derive from |
361
+ | --- | --- |
362
+ | **Deployment frequency** | Successful production deployments recorded through the GitHub Environment |
363
+ | **Lead time for changes** | Commit timestamp on `develop` → production deployment of the release containing it |
364
+ | **Change failure rate** | Releases that triggered a rollback, a canary failure, or a hotfix, over total releases |
365
+ | **Failed deployment recovery time** | Failure detected → traffic restored to the previous version |
366
+
367
+ Every input already exists: GitHub deployments and Releases, the tag and its timestamp, canary
368
+ analysis decisions, rollback records with their incident references, and alert firing and resolution
369
+ times. Nothing new needs instrumenting — the work is to make those events queryable and to publish the
370
+ trend. Compute the metrics on a schedule and post the trend to the Slack log channel.
371
+
372
+ Note the current definitions: **Failed Deployment Recovery Time** is DORA's renaming of what was
373
+ called MTTR, and it deliberately scopes to recovery from a failed deployment rather than to all
374
+ incidents. Track recovery from non-deployment incidents too, but do not conflate the two.
375
+
376
+ **Change failure rate and failed deployment recovery time are the pair that matters most here.**
377
+ They are DORA's stability measures, and they are exactly what the blue-green, canary and instant
378
+ rollback design exists to improve. A rising change failure rate means the Staging gate is not
379
+ catching what it should; a rising recovery time means the rollback path has decayed. Both are early
380
+ warnings that a control has stopped working, visible long before an incident proves it.
381
+
382
+ **Use these as trend indicators for this repository, not as targets or as comparisons between teams
383
+ or people.** DORA's own 2025 guidance dropped the Elite/High/Medium/Low ranking in favour of context —
384
+ and a metric that becomes a target gets gamed. Deployment frequency in particular is trivially
385
+ inflated and means nothing on its own; it is only informative alongside the stability pair.
386
+
387
+ ## Phase 4 — Security pipeline
388
+
389
+ Pipeline stages: Secret Scanning → SCA → SAST → IaC → Container → DAST → Continuous → Compliance.
390
+ Implement every stage that applies, mapped to the trigger where it can actually run.
391
+
392
+ **Use exactly these tools. Do not add alternatives that duplicate them.**
393
+
394
+ | Job | Tool |
395
+ | --- | --- |
396
+ | Secrets | GitHub Secret Protection — push protection, Copilot secret scanning, custom patterns |
397
+ | SCA | Dependabot + Dependency Review |
398
+ | SAST | CodeQL + Copilot Autofix |
399
+ | IaC | Checkov — covers Terraform, Kubernetes, Dockerfiles, Actions, ARM/Bicep |
400
+ | Container + SBOM | Trivy — image scanning and CycloneDX SBOM. **This is the blocking gate.** |
401
+ | Risk prioritisation | Grype (pin >= v0.88.0) — consumes the SBOM, ranks by CVSS + EPSS + CISA KEV. **Never gates.** |
402
+ | DAST | OWASP ZAP |
403
+ | Provenance | GitHub artifact attestations (L2) and `slsa-github-generator` (L3) |
404
+ | Posture | OSSF Scorecard |
405
+
406
+ Explicitly excluded as duplicates — do not reintroduce: tfsec (deprecated, merged into Trivy),
407
+ Terrascan, Kics, Template Analyzer, IaCFileScanner, Kubesec, Syft, Microsoft sbom-tool, Gitleaks,
408
+ Semgrep, Burp Suite (no CLI, cannot be automated), and the MSDO wrapper (it re-runs Checkov and Trivy
409
+ a second time). **Keep `trivy config` disabled** — Checkov owns IaC, Trivy owns images.
410
+
411
+ **Trivy and Grype are not duplicates and must not be run as two gates.** Trivy core has no EPSS or
412
+ CISA KEV support and its maintainers consider it out of scope, so exploitability-based ranking is a
413
+ genuine capability gap rather than overlap. Give them strictly separate roles: Trivy is the blocking
414
+ gate; Grype is a non-gating prioritiser that consumes the SBOM Trivy produced and reports what is
415
+ actually being exploited in the wild. Maintain exactly one allowlist, owned by Trivy. If Grype ever
416
+ starts failing builds, the design has been broken — two gates means double triage, and with no
417
+ reviewer that is how a gate gets disabled.
418
+
419
+ Stage mapping:
420
+
421
+ - **PR into `develop`** — secret scanning, dependency review, CodeQL, Checkov, and an action
422
+ SHA-pinning check. Source-level only. Keep it fast; a slow PR gate gets worked around.
423
+ - **Push to `develop`** — the above, plus build the artifact, generate its SBOM from the built image
424
+ rather than the source tree, scan the image with Trivy, and attest provenance at **SLSA Build L2**
425
+ using `actions/attest-build-provenance`.
426
+ - **`release/**` and `hotfix/**`** — the full gate, all bound to the promoted digest: Trivy scan of
427
+ that exact digest, SBOM, **SLSA Build L3** provenance via `slsa-github-generator`, OSSF Scorecard,
428
+ deploy to Staging, then OWASP ZAP against the deployed instance on a self-hosted runner. DAST needs a
429
+ running application and belongs here and nowhere else. Also run Grype against the candidate SBOM to
430
+ produce an exploitability-ranked risk report for the Release body — report only, never blocking. The
431
+ qualification record must not be issuable if any of the gating checks fail.
432
+ - **`main`** — verification only. Verify the L3 attestation, SBOM digest and qualification record all
433
+ bind to the digest being promoted. No new scans; re-scanning would describe a rebuilt artifact.
434
+ - **Scheduled** — re-scan released artifacts by running Grype against their **stored SBOMs**, which
435
+ needs no rebuild and no image pull, and re-scan the live production digest with Trivy. A CVE
436
+ published after release makes the deployed artifact vulnerable with no code change. Raise an issue on
437
+ new CISA KEV entries, high-EPSS findings, or new HIGH/CRITICAL severities. Feed Defender for Cloud and
438
+ Sentinel if configured.
439
+
440
+ Gating and evidence:
441
+
442
+ - Fail on **fixable** HIGH and CRITICAL findings. Upload SARIF from every scanner so results appear in
443
+ the Security tab rather than buried in logs.
444
+ - Suppressions are explicit and expiring: a committed allowlist with a reason and expiry per entry, and
445
+ the pipeline fails when an entry expires. Never suppress by lowering the global severity threshold.
446
+ With no reviewer, an unbounded allowlist is how a gate quietly becomes decorative.
447
+ - Commit an **AI Acceptable Use Policy** document. It is a documented policy and an attestation
448
+ recorded in the Release, not an automated check.
449
+ - Every scan emits a stable, named status check suitable for requiring by ruleset.
450
+
451
+ ### Audit evidence — every stage must produce a durable, reviewable report
452
+
453
+ An auditor must be able to answer, years after the fact: what was scanned, when, with which tool and
454
+ which vulnerability database, what was found, what was accepted and on whose authority, and proof that
455
+ the report describes the artifact that actually shipped. Design for that, not for a dashboard.
456
+
457
+ Three tiers, because retention and mutability differ:
458
+
459
+ 1. **Live triage — code scanning.** Every scanner uploads SARIF to GitHub code scanning. This is the
460
+ working view for fixing things. It is mutable live state — alerts get dismissed and resolved — so
461
+ it is never sufficient as the audit record on its own.
462
+ 2. **Run evidence — job summaries and workflow artifacts.** Every security job writes a rendered
463
+ human-readable summary to `$GITHUB_STEP_SUMMARY` and uploads its raw output as a workflow artifact.
464
+ Useful for debugging. Actions artifacts expire, so never treat them as the audit record.
465
+ 3. **Release evidence — the durable record.** For every release candidate, assemble one **evidence
466
+ bundle** and attach it to the GitHub Release. Release assets do not expire. This is the audit record.
467
+
468
+ The evidence bundle contains, for each pipeline stage:
469
+
470
+ - The raw machine-readable output exactly as the tool produced it (SARIF, CycloneDX, JSON).
471
+ - A rendered human-readable summary an auditor can read without tooling.
472
+ - A metadata record: tool name, **tool version, vulnerability database version and its timestamp**,
473
+ the exact command line, start and end time, exit status, and the workflow run URL. A finding count
474
+ is meaningless without the database version behind it — always record it.
475
+
476
+ And at bundle level:
477
+
478
+ - The artifact digest, source commit SHA, release version and branch.
479
+ - A `manifest.json` listing every file with its SHA-256.
480
+ - The suppression allowlist exactly as it stood at that moment, with each entry's reason, expiry and
481
+ the commit that introduced it.
482
+ - The DAST result, the Grype exploitability report, and the staging qualification record.
483
+ - The **attestation bundle** and **`trusted_root.jsonl`**, so the bundle carries everything needed to
484
+ verify itself without network access to GitHub or Sigstore.
485
+ - A `VERIFY.md` giving the expected OIDC issuer and certificate identity pattern, plus the exact
486
+ commands for both verification paths. The bundle must be self-describing: an auditor who receives
487
+ it on a USB stick, with no other context, should be able to verify it.
488
+
489
+ **Bind the bundle to the artifact.** Attest it with GitHub artifact attestations so
490
+ `gh attestation verify` proves the bundle belongs to that exact digest. An unbound report proves
491
+ nothing — an auditor cannot otherwise tell whether it describes what shipped. Record the bundle's own
492
+ digest in the release manifest and in the Release body.
493
+
494
+ **Point the gate at the evidence.** The staging qualification record must reference the evidence
495
+ bundle digest, so the single check that permits production is traceable to the complete evidence set.
496
+
497
+ **Do not archive every pull request scan.** That is noise and it is not what auditors ask for. That the
498
+ gate was continuously enforced is evidenced by the rulesets in git, the committed observed-contexts
499
+ file, and GitHub's own check-run history. Permanent evidence is required for what shipped.
500
+
501
+ **Scheduled re-scans produce dated reports too.** When Grype re-scans a stored SBOM and finds a new
502
+ CVE, file that report against the release it affects, so the record distinguishes what was known at
503
+ release time from what emerged later. An auditor reads that as diligence, not as a failure.
504
+
505
+ **External auditors have no GitHub access.** Evidence must therefore be independently verifiable by
506
+ someone outside the organisation, and by someone who cannot be given a GitHub account:
507
+
508
+ - Publish the **attestation bundle itself** as a downloadable Release asset, not only through the
509
+ GitHub attestation API, which requires authentication. GitHub attestations are recorded in
510
+ Sigstore's public transparency log, so an auditor holding the bundle and the artifact digest can
511
+ verify the whole chain against public infrastructure with no access to this repository and no
512
+ account here. Preserve that property deliberately — it means the auditor never has to trust any
513
+ system of ours, including any portal we build.
514
+ - Also publish **`trusted_root.jsonl`** (from `gh attestation trusted-root`) as a Release asset.
515
+ Without it, offline and air-gapped verification is impossible, which is exactly the environment a
516
+ defence-sector auditor is likely to be working in.
517
+ - **Document the expected signer identity** in `docs/security/audit-evidence.md`: the OIDC issuer
518
+ (`https://token.actions.githubusercontent.com`) and the exact certificate identity pattern for the
519
+ workflow that signs. A verification that does not pin the expected identity proves only that
520
+ *somebody* signed something — an auditor could accept a perfectly valid signature from an unrelated
521
+ repository. Pinning the identity is what makes the check meaningful.
522
+ - Give auditors **both** verification paths, since they will not all have the same tooling:
523
+ - `gh attestation verify <artifact> --bundle <bundle> --custom-trusted-root trusted_root.jsonl`
524
+ - `cosign verify-blob-attestation --bundle <bundle> --new-bundle-format --certificate-oidc-issuer <issuer> --certificate-identity-regexp <pattern> <artifact>`
525
+ The cosign path needs no GitHub tooling or account at all and is the vendor-neutral option. Note
526
+ that cosign is **not** used in the pipeline itself — signing is handled by the GitHub attestation
527
+ actions, and cosign is purely an auditor-side verification tool. Do not add a cosign installer to CI.
528
+ - Give the evidence bundle a **stable, versioned schema** — include `schema_version` in
529
+ `manifest.json` — so a downstream reader can parse releases from different points in time without
530
+ special-casing each one.
531
+ - Assume the bundle will be mirrored into a read-only auditor portal. That portal is a **projection**;
532
+ this repository's Releases and attestations remain the system of record, and the portal must be
533
+ fully rebuildable from them at any time. Never make the pipeline push findings into an external
534
+ database as their primary home, and never make a green portal a precondition for release.
535
+
536
+ ## Phase 5 — Governance as code
537
+
538
+ Commit each ruleset as JSON under `governance/rulesets/`, each the exact payload accepted by
539
+ `POST /repos/{owner}/{repo}/rulesets`. These files are the reviewable source of truth.
540
+
541
+ 1. **Protected branches** — `develop`, `main`, `release/**`, `hotfix/**`: `bypass_actors: []`, deletion
542
+ and non-fast-forward blocked, required status checks with `strict_required_status_checks_policy: true`
543
+ and `do_not_enforce_on_create: true`, and a pull request rule configured for a single maintainer —
544
+ `required_approving_review_count: 0`, `require_code_owner_review: false`,
545
+ `require_last_push_approval: false`, `dismiss_stale_reviews_on_push: true`. The pull request
546
+ requirement exists to force the checks to run, **not** to obtain anyone's approval. Do not raise the
547
+ approval count.
548
+ 2. **Main release gate** — a `main`-only ruleset requiring the staging qualification check, kept
549
+ separate so it is not demanded of `develop` or the release branches.
550
+ 3. **Release tag creation** — `refs/tags/v*`, creation restricted, with the **GitHub Actions app** as
551
+ the only bypass actor.
552
+ 4. **Release tag immutability** — `refs/tags/v*`, deletion and non-fast-forward blocked, no bypass
553
+ actors.
554
+
555
+ Add an idempotent script that applies these via `gh api`, resolving actor IDs at run time rather than
556
+ hard-coding them. Running it twice must be a no-op.
557
+
558
+ **Fail-closed sequencing.** Required status checks must be real job names you have already observed run
559
+ green. Author the workflows, merge them, confirm the exact context strings from
560
+ `gh api repos/{owner}/{repo}/commits/{sha}/check-runs --jq '.check_runs[].name'`, and only then install
561
+ the rulesets. Requiring a context that has never appeared blocks every merge including the fix — and
562
+ with no reviewer and no bypass actors, there is no manual escape. Commit the observed contexts as
563
+ evidence.
564
+
565
+ A required check that is skipped stays pending forever and deadlocks the ruleset. Every required job
566
+ must reach a terminal conclusion on every triggering event, including a deliberate successful
567
+ "nothing to do" path. **If the self-hosted runner group is unavailable, DAST must fail loudly with a
568
+ clear message rather than hang pending.**
569
+
570
+ **No required check may fail open.** A check that reports success when it could not actually do its
571
+ job is worse than no check at all, because it manufactures false assurance and nothing ever looks
572
+ wrong. Audit every required check against these patterns and fix any you find:
573
+
574
+ - `continue-on-error: true` on a job or step that a required check depends on.
575
+ - `|| true`, `|| echo`, or a trailing `exit 0` on a command whose failure is the thing being detected.
576
+ These are legitimate for diagnostics and cleanup; they are never acceptable on a validating command.
577
+ - Missing `set -euo pipefail` in a validation script, or a pipeline whose real exit status is masked
578
+ by a later command in the pipe.
579
+ - **Vacuous passes** — the most common and least visible case. A validation that iterates over files
580
+ and finds none, a `grep` that matches nothing, or a check guarded by `if [ -f ... ]` that silently
581
+ skips when the input is absent, all report success while having verified nothing. Every such check
582
+ must assert that it actually found something to inspect, and fail if its input set is unexpectedly
583
+ empty.
584
+ - An aggregator job using `if: always()` that tests only for `failure`. A dependency that is
585
+ `skipped` or `cancelled` is not `success` — compare explicitly against `success` for every needed
586
+ job, never against `failure`.
587
+
588
+ **Prove each check fails.** Observing a check run green establishes only that it can pass; it does not
589
+ establish that it can fail. Before requiring a check, run it once against a deliberate violation and
590
+ confirm it goes red — a malformed branch name, an unpinned action, a known-vulnerable dependency, a
591
+ planted dummy secret. Record that negative result alongside the observed contexts. A check that has
592
+ only ever been seen passing has never been shown to work at all.
593
+
594
+ Treat a discovered fail-open as a real defect even when nothing is currently broken, because by
595
+ definition it produces no symptom until the moment it matters. If changing `.github/workflows/` is
596
+ itself gated and you cannot land the fix in this session, **file a GitHub issue describing the exact
597
+ defect, the affected checks, and the reproduction — do not silently leave it undocumented.**
598
+
599
+ ## Phase 6 — Documentation
600
+
601
+ - `docs/operations/github-governance.md` — the exact required contexts, which ruleset requires what,
602
+ and why. Keep it in step with the JSON.
603
+ - `CONTRIBUTING.md` — branching model, branch naming, commit and PR conventions, how to cut a release.
604
+ - `docs/security/scanning.md` — the tool set, the stage mapping, and the suppression policy.
605
+ - `docs/operations/alerting.md` — every alert rule, its severity, its threshold and the reasoning
606
+ behind it, the Slack routing per severity, and how to verify the alert path end to end.
607
+ - `docs/operations/service-health.md` — every component, its health endpoint, what its deep check
608
+ actually exercises, its dependencies, and the recovery order implied by them. This is read during
609
+ an incident, so write it to be scanned under pressure, not studied.
610
+ - `docs/security/audit-evidence.md` — where evidence lives, what the evidence bundle contains, the
611
+ **expected OIDC issuer and certificate identity pattern**, and the exact commands an auditor runs to
612
+ retrieve a release's bundle and verify its attestation independently, without needing me. Give both
613
+ the `gh attestation verify` and the `cosign verify-blob-attestation` paths, and state explicitly
614
+ that verification which does not pin the expected identity proves only that *somebody* signed
615
+ *something* — a valid signature from an unrelated repository would otherwise pass.
616
+ - `docs/ai-acceptable-use.md` — the AI acceptable use policy.
617
+ - `docs/runbooks/` — break-glass, production rollback, failed-release repair, and canary failure. The
618
+ rollback runbook must state that rollback is ungated and requires no approval, and must have been
619
+ rehearsed rather than merely written. The break-glass runbook matters most: with no reviewer and no
620
+ bypass actors, I need a written, logged way back in.
621
+
622
+ ## Deliverables
623
+
624
+ 1. Phase 0 classification and proposed plan — **stop and get my approval.**
625
+ 2. A PR adding workflows, `governance/rulesets/*.json`, the apply script, docs and runbooks.
626
+ 3. Evidence every required context ran green, with the exact context strings quoted, **and evidence
627
+ that each one goes red against a deliberate violation.**
628
+ 4. Rulesets installed only after (3), then re-verified by reading the live rulesets back.
629
+ 5. A summary of what is enforced, what is advisory, and what was skipped for this repo and why.
630
+
631
+ ## Constraints
632
+
633
+ - Never weaken or delete an existing protection to make something pass. Raise it with me instead.
634
+ - Never force-push, delete or rewrite a protected branch or an existing tag.
635
+ - Never enable a ruleset whose required contexts have not been observed green.
636
+ - Never propose org-level rulesets, org-level required workflows, or org-wide App installations.
637
+ Everything is repository-scoped by design.
638
+ - Prefer fewer genuinely enforced controls over a large set that gets bypassed. Tell me honestly which
639
+ controls would be theatre in this repository.
640
+
641
+ ## Liftoff activation protocol
642
+
643
+ This policy is a generated local handoff. Its presence does not mean that any
644
+ branch, check, ruleset, security feature, environment, deployment, monitor, or
645
+ alert is active.
646
+
647
+ ### Prerequisites
648
+
649
+ Do not begin activation until the repository is committed, pushed, and
650
+ resolvable as a GitHub repository. If any prerequisite is missing, report it and
651
+ stop without mutation.
652
+
653
+ ### Read-only Phase 0
654
+
655
+ Inspect and report all of the following with evidence before changing anything:
656
+
657
+ 1. Repository owner/name, remote identity, visibility, default branch, current
658
+ local branch, working-tree state, and current `main` tip.
659
+ 2. Artifact forms, languages, package managers, manifests, locks, build
660
+ commands, test commands, lint commands, generated environments, health
661
+ endpoints, and operations or infrastructure files.
662
+ 3. Existing local and remote refs, GitFlow compatibility, workflows, exact job
663
+ and check-run names, rulesets, tags, releases, GitHub Environments, deployment
664
+ history, and rollback paths.
665
+ 4. Secret Protection, Dependabot, Dependency Review, CodeQL, Copilot Autofix,
666
+ Checkov, Trivy, Grype, ZAP, attestations, SLSA, Scorecard, licenses, and every
667
+ required input or suppression policy.
668
+ 5. GitHub Actions runner labels and whether a private-network runner can reach
669
+ Staging.
670
+ 6. Live deployment mechanisms, parallel-version support, version-specific
671
+ origins, traffic volume, statistically valid canary capacity, and provider
672
+ status sources.
673
+ 7. Monitoring signals, alert rules, severity routing, Slack delivery,
674
+ heartbeat coverage, alert-fire tests, dashboards, shallow/deep component
675
+ health, dependency graph, recovery order, deployed versions/digests, and DORA
676
+ event sources.
677
+
678
+ Report every gap, blocker, inapplicable control, and GitFlow-versus-continuous-
679
+ delivery conflict. Propose the current `main` SHA as the activation baseline for
680
+ an existing repository and provide an ordered implementation plan.
681
+
682
+ **STOP FOR EXPLICIT USER APPROVAL.** Phase 0 is read-only. Before approval, do
683
+ not write files, create a framework change, branch, commit, tag, release,
684
+ workflow, environment, ruleset, issue, deployment, cloud resource, monitor,
685
+ alert, or Slack route.
686
+
687
+ ### Post-approval implementation
688
+
689
+ After approval, create a new governance change using the project's selected
690
+ OpenSpec or Spec Kit workflow. The generated Liftoff policy remains an input;
691
+ Liftoff does not own, name, restore, or recreate that active change.
692
+
693
+ Author workflows, exact ruleset payloads, runbooks, and documentation first.
694
+ Observe every proposed required context green on all applicable paths, then
695
+ prove that exact context deliberately red with a controlled violation. Treat
696
+ skipped or cancelled dependencies as not successful. Apply repository-scoped
697
+ rulesets idempotently last, then read the live rulesets and required contexts
698
+ back from GitHub.
699
+
700
+ Record the explicitly approved pre-governance `main` SHA in the user-owned
701
+ `governance/activation-baseline.json`. If `main` advanced since Phase 0, stop
702
+ and obtain a newly discovered and approved baseline. Apply release/tag anomaly
703
+ checks only to governed production commits after that SHA. Never invent a
704
+ historical release, move a tag, rewrite history, or treat the activation record
705
+ as Liftoff-owned.