mandrel-platform 0.24.0 → 0.26.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/package.json +2 -2
- package/scripts/check-pin-drift.mjs +66 -1
- package/scripts/check-pin-drift.test.mjs +125 -0
- package/scripts/check-workflow-gh-flags.mjs +172 -0
- package/scripts/check-workflow-gh-flags.test.mjs +86 -0
- package/scripts/deploy-boot-smoke.mjs +116 -36
- package/scripts/deploy-boot-smoke.test.mjs +138 -17
- package/scripts/platform-repair.mjs +38 -2
- package/scripts/platform-repair.test.mjs +113 -0
- package/scripts/semgrep-requirements.txt +169 -0
- package/templates/workflows/deploy-staging-run.yml +44 -4
- package/templates/workflows/deploy-staging.yml +68 -9
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# Hash-pinned lockfile for the pr-quality SAST (semgrep) step.
|
|
2
|
+
#
|
|
3
|
+
# Target platform : Linux x86_64 / CPython 3.12 (GitHub Actions ubuntu-latest)
|
|
4
|
+
# Tool : semgrep 1.97.0 (+ complete transitive closure)
|
|
5
|
+
# Consumed via : pip install --require-hashes -r scripts/semgrep-requirements.txt
|
|
6
|
+
#
|
|
7
|
+
# Every requirement below is pinned with `==` and carries at least one
|
|
8
|
+
# sha256 hash, as `--require-hashes` demands. The closure was resolved for
|
|
9
|
+
# the linux/cp312 target specifically (via `pip download` with explicit
|
|
10
|
+
# --platform manylinux/musllinux + --python-version 3.12 --abi cp312), NOT
|
|
11
|
+
# from the local interpreter, so the native wheels (semgrep, protobuf,
|
|
12
|
+
# rpds-py, wrapt, charset-normalizer, ruamel.yaml.clib) are the linux x86_64
|
|
13
|
+
# cp312-compatible artifacts.
|
|
14
|
+
#
|
|
15
|
+
# setuptools is intentionally included: the workflow installs it alongside
|
|
16
|
+
# semgrep so opentelemetry's transitive `pkg_resources` import works on
|
|
17
|
+
# Python >=3.12 (which no longer ships setuptools by default).
|
|
18
|
+
#
|
|
19
|
+
# Regenerate with `pip download semgrep==<ver> setuptools --only-binary=:all:
|
|
20
|
+
# --platform manylinux2014_x86_64 --platform manylinux_2_17_x86_64
|
|
21
|
+
# --platform any --python-version 3.12 --implementation cp --abi cp312`.
|
|
22
|
+
|
|
23
|
+
attrs==26.1.0 \
|
|
24
|
+
--hash=sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309
|
|
25
|
+
|
|
26
|
+
boltons==21.0.0 \
|
|
27
|
+
--hash=sha256:b9bb7b58b2b420bbe11a6025fdef6d3e5edc9f76a42fb467afe7ca212ef9948b
|
|
28
|
+
|
|
29
|
+
bracex==3.0 \
|
|
30
|
+
--hash=sha256:3833e61c2f092d5aa0468fa2e6c6e990a306185abf763b6d122f0158e59c58a5
|
|
31
|
+
|
|
32
|
+
certifi==2026.6.17 \
|
|
33
|
+
--hash=sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db
|
|
34
|
+
|
|
35
|
+
charset-normalizer==3.4.7 \
|
|
36
|
+
--hash=sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116
|
|
37
|
+
|
|
38
|
+
click==8.4.2 \
|
|
39
|
+
--hash=sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76
|
|
40
|
+
|
|
41
|
+
click-option-group==0.5.9 \
|
|
42
|
+
--hash=sha256:ad2599248bd373e2e19bec5407967c3eec1d0d4fc4a5e77b08a0481e75991080
|
|
43
|
+
|
|
44
|
+
colorama==0.4.6 \
|
|
45
|
+
--hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
|
|
46
|
+
|
|
47
|
+
defusedxml==0.7.1 \
|
|
48
|
+
--hash=sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61
|
|
49
|
+
|
|
50
|
+
deprecated==1.3.1 \
|
|
51
|
+
--hash=sha256:597bfef186b6f60181535a29fbe44865ce137a5079f295b479886c82729d5f3f
|
|
52
|
+
|
|
53
|
+
exceptiongroup==1.2.2 \
|
|
54
|
+
--hash=sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b
|
|
55
|
+
|
|
56
|
+
face==26.0.1 \
|
|
57
|
+
--hash=sha256:ab0a83c37c9789dce658a67a9a80eafaa113c9ec37c5a9d950ff5480542a062d
|
|
58
|
+
|
|
59
|
+
glom==22.1.0 \
|
|
60
|
+
--hash=sha256:5339da206bf3532e01a83a35aca202960ea885156986d190574b779598e9e772
|
|
61
|
+
|
|
62
|
+
googleapis-common-protos==1.75.0 \
|
|
63
|
+
--hash=sha256:961ed60399c457ceb0ee8f285a84c870aabc9c6a832b9d37bb281b5bebde43ed
|
|
64
|
+
|
|
65
|
+
idna==3.18 \
|
|
66
|
+
--hash=sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2
|
|
67
|
+
|
|
68
|
+
importlib-metadata==7.1.0 \
|
|
69
|
+
--hash=sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570
|
|
70
|
+
|
|
71
|
+
jsonschema==4.26.0 \
|
|
72
|
+
--hash=sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce
|
|
73
|
+
|
|
74
|
+
jsonschema-specifications==2025.9.1 \
|
|
75
|
+
--hash=sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe
|
|
76
|
+
|
|
77
|
+
markdown-it-py==4.2.0 \
|
|
78
|
+
--hash=sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a
|
|
79
|
+
|
|
80
|
+
mdurl==0.1.2 \
|
|
81
|
+
--hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8
|
|
82
|
+
|
|
83
|
+
opentelemetry-api==1.25.0 \
|
|
84
|
+
--hash=sha256:757fa1aa020a0f8fa139f8959e53dec2051cc26b832e76fa839a6d76ecefd737
|
|
85
|
+
|
|
86
|
+
opentelemetry-exporter-otlp-proto-common==1.25.0 \
|
|
87
|
+
--hash=sha256:15637b7d580c2675f70246563363775b4e6de947871e01d0f4e3881d1848d693
|
|
88
|
+
|
|
89
|
+
opentelemetry-exporter-otlp-proto-http==1.25.0 \
|
|
90
|
+
--hash=sha256:2eca686ee11b27acd28198b3ea5e5863a53d1266b91cda47c839d95d5e0541a6
|
|
91
|
+
|
|
92
|
+
opentelemetry-instrumentation==0.46b0 \
|
|
93
|
+
--hash=sha256:89cd721b9c18c014ca848ccd11181e6b3fd3f6c7669e35d59c48dc527408c18b
|
|
94
|
+
|
|
95
|
+
opentelemetry-instrumentation-requests==0.46b0 \
|
|
96
|
+
--hash=sha256:a8c2472800d8686f3f286cd524b8746b386154092e85a791ba14110d1acc9b81
|
|
97
|
+
|
|
98
|
+
opentelemetry-proto==1.25.0 \
|
|
99
|
+
--hash=sha256:f07e3341c78d835d9b86665903b199893befa5e98866f63d22b00d0b7ca4972f
|
|
100
|
+
|
|
101
|
+
opentelemetry-sdk==1.25.0 \
|
|
102
|
+
--hash=sha256:d97ff7ec4b351692e9d5a15af570c693b8715ad78b8aafbec5c7100fe966b4c9
|
|
103
|
+
|
|
104
|
+
opentelemetry-semantic-conventions==0.46b0 \
|
|
105
|
+
--hash=sha256:6daef4ef9fa51d51855d9f8e0ccd3a1bd59e0e545abe99ac6203804e36ab3e07
|
|
106
|
+
|
|
107
|
+
opentelemetry-util-http==0.46b0 \
|
|
108
|
+
--hash=sha256:8dc1949ce63caef08db84ae977fdc1848fe6dc38e6bbaad0ae3e6ecd0d451629
|
|
109
|
+
|
|
110
|
+
packaging==26.2 \
|
|
111
|
+
--hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e
|
|
112
|
+
|
|
113
|
+
peewee==3.19.0 \
|
|
114
|
+
--hash=sha256:de220b94766e6008c466e00ce4ba5299b9a832117d9eb36d45d0062f3cfd7417
|
|
115
|
+
|
|
116
|
+
protobuf==4.25.9 \
|
|
117
|
+
--hash=sha256:438c636de8fb706a0de94a12a268ef1ae8f5ba5ae655a7671fcda5968ba3c9be
|
|
118
|
+
|
|
119
|
+
pygments==2.20.0 \
|
|
120
|
+
--hash=sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176
|
|
121
|
+
|
|
122
|
+
referencing==0.37.0 \
|
|
123
|
+
--hash=sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231
|
|
124
|
+
|
|
125
|
+
requests==2.34.2 \
|
|
126
|
+
--hash=sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0
|
|
127
|
+
|
|
128
|
+
rich==13.5.3 \
|
|
129
|
+
--hash=sha256:9257b468badc3d347e146a4faa268ff229039d4c2d176ab0cffb4c4fbc73d5d9
|
|
130
|
+
|
|
131
|
+
rpds-py==2026.6.3 \
|
|
132
|
+
--hash=sha256:ecabd69db66de867690f9797f2f8fa27ba501bbc24540cbdbdc649cd15888ba6
|
|
133
|
+
|
|
134
|
+
ruamel.yaml==0.17.40 \
|
|
135
|
+
--hash=sha256:b16b6c3816dff0a93dca12acf5e70afd089fa5acb80604afd1ffa8b465b7722c
|
|
136
|
+
|
|
137
|
+
ruamel.yaml.clib==0.2.15 \
|
|
138
|
+
--hash=sha256:11e5499db1ccbc7f4b41f0565e4f799d863ea720e01d3e99fa0b7b5fcd7802c9
|
|
139
|
+
|
|
140
|
+
semgrep==1.97.0 \
|
|
141
|
+
--hash=sha256:996fe0b2bfac3a4d4511e470fdf5f3bca96b1f794f398e0336c8388802c218de
|
|
142
|
+
|
|
143
|
+
# PINNED to 80.9.0 (the last release that still SHIPS `pkg_resources`):
|
|
144
|
+
# setuptools 81+ removed the vendored `pkg_resources` module, but
|
|
145
|
+
# semgrep 1.97.0's transitive `opentelemetry-instrumentation==0.46b0` imports
|
|
146
|
+
# it at load. A py3.12 venv seeds no setuptools, so the lockfile must supply a
|
|
147
|
+
# pkg_resources-bearing one — 83.0.0 broke the SAST step at runtime with
|
|
148
|
+
# `ModuleNotFoundError: No module named 'pkg_resources'`. Do NOT bump past 80.x
|
|
149
|
+
# without confirming pkg_resources is present (or bumping opentelemetry off it).
|
|
150
|
+
setuptools==80.9.0 \
|
|
151
|
+
--hash=sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922
|
|
152
|
+
|
|
153
|
+
tomli==2.0.2 \
|
|
154
|
+
--hash=sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38
|
|
155
|
+
|
|
156
|
+
typing-extensions==4.16.0 \
|
|
157
|
+
--hash=sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8
|
|
158
|
+
|
|
159
|
+
urllib3==2.7.0 \
|
|
160
|
+
--hash=sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897
|
|
161
|
+
|
|
162
|
+
wcmatch==8.5.2 \
|
|
163
|
+
--hash=sha256:17d3ad3758f9d0b5b4dedc770b65420d4dac62e680229c287bf24c9db856a478
|
|
164
|
+
|
|
165
|
+
wrapt==1.17.3 \
|
|
166
|
+
--hash=sha256:042ec3bb8f319c147b1301f2393bc19dba6e176b7da446853406d041c36c7828
|
|
167
|
+
|
|
168
|
+
zipp==4.1.0 \
|
|
169
|
+
--hash=sha256:25ad4e16390cd314347dd8f1de67a2ac538ae658ed4ab9db16029c07c188e97f
|
|
@@ -51,16 +51,56 @@ on:
|
|
|
51
51
|
permissions:
|
|
52
52
|
contents: read
|
|
53
53
|
|
|
54
|
-
# Serialize staging deploys:
|
|
55
|
-
#
|
|
56
|
-
#
|
|
54
|
+
# Serialize staging deploys: QUEUE, don't cancel (Story #284 / audit H2). An
|
|
55
|
+
# in-flight `wrangler d1 migrations apply` must never be cancelled mid-apply by
|
|
56
|
+
# the next dispatch — a half-applied forward-only migration has no automatic
|
|
57
|
+
# restore. `cancel-in-progress: false` lets a second dispatch wait for the
|
|
58
|
+
# first to finish. (The dispatcher half keeps `cancel-in-progress: true`:
|
|
59
|
+
# cancelling a superseded PENDING dispatch is safe.) The shared
|
|
60
|
+
# deploy-cloudflare.yml additionally serializes per-environment.
|
|
57
61
|
concurrency:
|
|
58
62
|
group: deploy-staging-run
|
|
59
|
-
cancel-in-progress:
|
|
63
|
+
cancel-in-progress: false
|
|
60
64
|
|
|
61
65
|
jobs:
|
|
66
|
+
# M4: sha-drift preflight (Story #284). The CI-green gate is TOCTOU-racy — a
|
|
67
|
+
# commit A goes green and dispatches, but commit B (CI pending, possibly
|
|
68
|
+
# later red) may already be on main, so the deploy would ship B under A's
|
|
69
|
+
# green credential. When `sha` is supplied (dispatcher path), assert the main
|
|
70
|
+
# tip still equals it and FAIL loudly on drift, naming both SHAs. A job with
|
|
71
|
+
# `uses:` cannot carry `steps:`, so this preflight is a SEPARATE preceding
|
|
72
|
+
# job that the `uses:` deploy job `needs:`. Manual dispatches without `sha`
|
|
73
|
+
# skip the assertion (the operator is deploying the current tip on purpose).
|
|
74
|
+
sha-drift-preflight:
|
|
75
|
+
name: Sha-drift preflight (deploy what CI verified)
|
|
76
|
+
runs-on: ubuntu-latest
|
|
77
|
+
timeout-minutes: 5
|
|
78
|
+
steps:
|
|
79
|
+
- name: Checkout main tip
|
|
80
|
+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
81
|
+
with:
|
|
82
|
+
ref: main
|
|
83
|
+
persist-credentials: false
|
|
84
|
+
- name: Assert HEAD matches the CI-verified sha
|
|
85
|
+
env:
|
|
86
|
+
INPUT_SHA: ${{ inputs.sha }}
|
|
87
|
+
shell: bash
|
|
88
|
+
run: |
|
|
89
|
+
set -euo pipefail
|
|
90
|
+
if [ -z "${INPUT_SHA}" ]; then
|
|
91
|
+
echo "No sha input — manual dispatch deploying the current main tip; skipping sha-drift preflight."
|
|
92
|
+
exit 0
|
|
93
|
+
fi
|
|
94
|
+
head="$(git rev-parse HEAD)"
|
|
95
|
+
if [ "${head}" != "${INPUT_SHA}" ]; then
|
|
96
|
+
echo "::error::sha drift: main tip is ${head} but the dispatched (CI-verified) sha is ${INPUT_SHA}. main advanced after CI went green; refusing to deploy an unverified tip." >&2
|
|
97
|
+
exit 1
|
|
98
|
+
fi
|
|
99
|
+
echo "sha-drift preflight OK: main tip ${head} matches the CI-verified sha ${INPUT_SHA}."
|
|
100
|
+
|
|
62
101
|
deploy:
|
|
63
102
|
name: Staging deploy (shared deploy-cloudflare.yml)
|
|
103
|
+
needs: [sha-drift-preflight]
|
|
64
104
|
uses: dsj1984/mandrel-platform/.github/workflows/deploy-cloudflare.yml@<MANDREL_PLATFORM_SHA> # <MANDREL_PLATFORM_TAG>
|
|
65
105
|
with:
|
|
66
106
|
environment: staging
|
|
@@ -71,10 +71,23 @@ jobs:
|
|
|
71
71
|
name: Dispatch staging deploy on CI-green
|
|
72
72
|
runs-on: ubuntu-latest
|
|
73
73
|
timeout-minutes: 5
|
|
74
|
-
# CI-green gate
|
|
75
|
-
#
|
|
76
|
-
#
|
|
77
|
-
|
|
74
|
+
# CI-green gate — three load-bearing conditions, all required (Story #284):
|
|
75
|
+
# 1. `conclusion == 'success'` — `workflow_run` fires on both success and
|
|
76
|
+
# failure; without this a red main would still deploy.
|
|
77
|
+
# 2. `event == 'push'` — the upstream CI run must itself have been a push
|
|
78
|
+
# to the repo, not a `pull_request` run. `workflow_run.branches:
|
|
79
|
+
# [main]` filters on the *head branch NAME*, so a fork PR whose head
|
|
80
|
+
# branch is literally named `main` with green CI otherwise satisfies
|
|
81
|
+
# the branch filter and the success guard (audit M3).
|
|
82
|
+
# 3. `head_repository.full_name == github.repository` — the CI run must
|
|
83
|
+
# have originated from THIS repo, not a fork. Belt-and-suspenders with
|
|
84
|
+
# condition 2: even a same-name fork branch cannot spoof same-repo
|
|
85
|
+
# provenance, so an external contributor can no longer attacker-time a
|
|
86
|
+
# staging deploy off their fork's green CI.
|
|
87
|
+
if: >-
|
|
88
|
+
${{ github.event.workflow_run.conclusion == 'success'
|
|
89
|
+
&& github.event.workflow_run.event == 'push'
|
|
90
|
+
&& github.event.workflow_run.head_repository.full_name == github.repository }}
|
|
78
91
|
steps:
|
|
79
92
|
- name: Dispatch deploy-staging-run.yml (workflow_dispatch)
|
|
80
93
|
env:
|
|
@@ -84,8 +97,54 @@ jobs:
|
|
|
84
97
|
shell: bash
|
|
85
98
|
run: |
|
|
86
99
|
set -euo pipefail
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
100
|
+
|
|
101
|
+
# L3: the dispatch is otherwise fire-and-forget — a transient GitHub
|
|
102
|
+
# API 5xx fails `gh workflow run`, and the only signal is a red run in
|
|
103
|
+
# a low-visibility dispatcher while that green commit never reaches
|
|
104
|
+
# staging. Retry with backoff, then VERIFY a run was actually created
|
|
105
|
+
# (a 2xx from the dispatch API is not proof a run materialized).
|
|
106
|
+
dispatched=false
|
|
107
|
+
for attempt in 1 2 3; do
|
|
108
|
+
if gh workflow run deploy-staging-run.yml \
|
|
109
|
+
--repo "${REPO}" \
|
|
110
|
+
--ref main \
|
|
111
|
+
-f sha="${SHA}"; then
|
|
112
|
+
dispatched=true
|
|
113
|
+
break
|
|
114
|
+
fi
|
|
115
|
+
echo "::warning::gh workflow run attempt ${attempt} failed; retrying after backoff."
|
|
116
|
+
sleep $((attempt * 5))
|
|
117
|
+
done
|
|
118
|
+
|
|
119
|
+
if [ "${dispatched}" != "true" ]; then
|
|
120
|
+
echo "::error::Failed to dispatch deploy-staging-run.yml after 3 attempts for ${SHA}." >&2
|
|
121
|
+
exit 1
|
|
122
|
+
fi
|
|
123
|
+
|
|
124
|
+
# Verify a runner workflow run was actually created. The dispatch API
|
|
125
|
+
# returns before the run row is queryable, so poll `gh run list` for a
|
|
126
|
+
# recent workflow_dispatch run of the runner workflow. Fail loudly if
|
|
127
|
+
# none appears — a silent non-creation is exactly the failure mode
|
|
128
|
+
# this check exists to surface.
|
|
129
|
+
created=false
|
|
130
|
+
for attempt in 1 2 3 4 5; do
|
|
131
|
+
count="$(gh run list \
|
|
132
|
+
--repo "${REPO}" \
|
|
133
|
+
--workflow deploy-staging-run.yml \
|
|
134
|
+
--event workflow_dispatch \
|
|
135
|
+
--limit 5 \
|
|
136
|
+
--json databaseId --jq 'length' 2>/dev/null || echo 0)"
|
|
137
|
+
if [ "${count:-0}" -gt 0 ]; then
|
|
138
|
+
created=true
|
|
139
|
+
break
|
|
140
|
+
fi
|
|
141
|
+
echo "Run not visible yet (attempt ${attempt}); waiting for the run row to appear."
|
|
142
|
+
sleep $((attempt * 3))
|
|
143
|
+
done
|
|
144
|
+
|
|
145
|
+
if [ "${created}" != "true" ]; then
|
|
146
|
+
echo "::error::Dispatched deploy-staging-run.yml for ${SHA} but no runner workflow run appeared via 'gh run list'." >&2
|
|
147
|
+
exit 1
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
echo "Dispatched and verified staging deploy for ${SHA} (deploy runs on workflow_dispatch so environment: jobs execute)."
|