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.
@@ -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: only the freshest dispatch should reach the
55
- # staging surfaces. The shared deploy-cloudflare.yml additionally serializes
56
- # per-environment.
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: true
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: dispatch the deploy ONLY when the upstream CI run
75
- # concluded 'success'. `workflow_run` fires on both success and failure, so
76
- # this guard is load-bearing — without it a red main would still deploy.
77
- if: ${{ github.event.workflow_run.conclusion == 'success' }}
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
- gh workflow run deploy-staging-run.yml \
88
- --repo "${REPO}" \
89
- --ref main \
90
- -f sha="${SHA}"
91
- echo "Dispatched staging deploy for ${SHA} (deploy runs on workflow_dispatch so environment: jobs execute)."
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)."