ci(adr-117): kics-compatible workflow comments + fix-marker guards

- KICS error fix (.github/workflows/pip-release.yml:20): the inline
  `gcloud secrets versions access --secret=PYPI_TOKEN ...` runbook
  in the workflow header was triggering KICS' generic-secret regex
  on the literal `PYPI_TOKEN` substring. Moved the refresh runbook
  to docs/integrations/pypi-release.md (with the BOM-stripping
  `tr` step that fixed the production publish) and replaced the
  inline block with a pointer.

- Three new fix-marker guards in scripts/fix-markers.json so the
  next person to touch this code can't silently regress what
  PR #786 just shipped:

  * RuView#786-tombstone-import — the tombstone __init__.py must
    `raise ImportError`, must mention the v2 install hint, must
    point at the repo URL, AND must NOT contain `def`/`class`/
    `import wifi_densepose` (forbid patterns prevent accidental
    bloating into a real module that loads partway before failing).

  * RuView#786-tombstone-smoke-cwd — pip-release.yml must `cd /tmp`
    before the tombstone smoke-test import, because the legacy
    `./wifi_densepose/__init__.py` at repo root would otherwise
    shadow the venv install. This was the root cause of run
    26366648768; locking it in.

  * RuView#786-pypi-token-auth — the workflow must use
    `password: ${{ secrets.PYPI_API_TOKEN }}` and must NOT carry
    `id-token: write`. The project authenticates via API token,
    not OIDC; a partial OIDC migration would 403 silently.

Local check: all 25 markers pass.

Refs: docs/adr/ADR-117-pip-wifi-densepose-modernization.md
Refs: #786

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
ruv 2026-05-24 12:53:25 -04:00
parent b71d243b42
commit 51b3433471
3 changed files with 108 additions and 7 deletions

View File

@ -13,13 +13,10 @@
# 1. cut tag `v1.99.0-pip` → publishes the tombstone wheel first
# 2. cut tag `v2.0.0-pip` → publishes the PyO3 v2 wheel matrix
#
# Publishes via PyPI API token stored in the `PYPI_API_TOKEN`
# GitHub Actions secret. The token value comes from the GCP Secret
# Manager entry `PYPI_TOKEN` in project `cognitum-20260110`; refresh
# with:
# gcloud secrets versions access latest --secret=PYPI_TOKEN \
# --project=cognitum-20260110 \
# | gh secret set PYPI_API_TOKEN --repo ruvnet/RuView
# Publishes via the `PYPI_API_TOKEN` GitHub Actions secret. The
# token-refresh runbook (GCP Secret Manager → gh secret set) lives in
# docs/integrations/pypi-release.md so KICS does not flag the
# secret name as a generic-secret literal in the workflow.
#
# Q3 (witness hash v2 — open in ADR-117 §11.3) MUST be resolved
# before the first v2.0.0 publish. When v2 lands, add a parallel

View File

@ -0,0 +1,64 @@
# PyPI release runbook — `wifi-densepose` + `ruview`
Operations doc for the `.github/workflows/pip-release.yml` CI workflow.
## Auth
The workflow uses one GitHub Actions secret named `PYPI_API_TOKEN`.
It's a project-token issued by the rUv PyPI account with upload
scope for both `wifi-densepose` and `ruview`.
## Refreshing the token
The canonical copy of the token lives in GCP Secret Manager,
project `cognitum-20260110`, entry name `PYPI_TOKEN`. To push a
fresh copy into GitHub Actions:
```bash
gcloud secrets versions access latest \
--secret=PYPI_TOKEN \
--project=cognitum-20260110 \
| tr -d '\r\n\xef\xbb\xbf' \
| gh secret set PYPI_API_TOKEN --repo ruvnet/RuView
```
The `tr` step strips any BOM / CRLF that PowerShell pipes or
Windows editors may have introduced — without it, twine fails with
`UnicodeEncodeError: 'latin-1' codec can't encode character ''`.
## Triggering a release
Two paths:
- **Tag push**`git tag v2.X.Y-pip && git push origin v2.X.Y-pip`
publishes the v2 wheel matrix. `v1.99.0-pip` triggers the tombstone
job instead.
- **Manual dispatch** — `gh workflow run pip-release.yml --ref <branch>
-f target=v2-wheels -f publish_to=pypi`. Use `publish_to=testpypi`
for a dry-run target if a TestPyPI token is also set as
`TESTPYPI_API_TOKEN`.
## Release-day sequence
Per ADR-117 §7.3, the tombstone publishes first so it claims the
"current" slot in pip's resolver:
1. `git tag v1.99.0-pip && git push origin v1.99.0-pip`
tombstone live at `https://pypi.org/project/wifi-densepose/1.99.0/`
2. Verify: `pip install wifi-densepose==1.99.0; python -c "import
wifi_densepose"` → ImportError with migration URL.
3. `git tag v2.0.0-pip && git push origin v2.0.0-pip` → v2 wheel
matrix live at `https://pypi.org/project/wifi-densepose/2.0.0/`.
4. (Optional, in lock-step) build + publish a matching `ruview`
release from `python/ruview-meta/` so the meta-package version
stays pinned to the same wifi-densepose version.
## Off-loop manual gates
- **Q3** (ADR-117 §11.3) — generate `expected_features_v2.sha256`
from the v2 Rust pipeline before any v2 publish.
- **OIDC Trusted Publisher** — not used. The workflow is token-based;
this is a deliberate choice to keep the secret refresh entirely in
GCP. If the project migrates to OIDC later, remove `password:`
from `pypa/gh-action-pypi-publish` calls and add the publisher
registration on pypi.org.

View File

@ -233,6 +233,46 @@
],
"rationale": "At edge tier>=2 on N16R8 PSRAM boards, process_frame() runs update_multi_person_vitals() (4 persons × 256 history samples) plus wasm_runtime_on_frame() back-to-back. The vTaskDelay(1) in edge_task() only fires AFTER process_frame() fully returns — if process_frame() takes >5 s (common on PSRAM-backed boards under sustained 30 pps CSI load), IDLE1 on Core 1 never runs and the Task Watchdog Timer fires. The fix adds two vTaskDelay(1) calls inside process_frame(), gated on tier>=2, at the multi-person vitals boundary and after WASM dispatch. Removing them re-opens the WDT storm on N16R8 hardware.",
"ref": "https://github.com/ruvnet/RuView/issues/683"
},
{
"id": "RuView#786-tombstone-import",
"title": "Tombstone (v1.99.0) __init__.py must raise ImportError with migration URL on import",
"files": ["python/tombstone/src/wifi_densepose/__init__.py"],
"require": [
"raise ImportError(",
"pip install wifi-densepose==2.0.0",
"github.com/ruvnet/RuView"
],
"forbid": [
"/^def\\s/",
"/^class\\s/",
"/^import\\s+wifi_densepose/"
],
"rationale": "ADR-117 §7.2 — the v1.99.0 tombstone wheel exists solely to raise a legible ImportError when v1.x users upgrade. If a future refactor adds real code (def / class / imports beyond the bare raise), the module may load partway before failing, breaking the migration narrative. The require patterns lock in the raise + the v2 install hint + the repo URL.",
"ref": "https://github.com/ruvnet/RuView/pull/786"
},
{
"id": "RuView#786-tombstone-smoke-cwd",
"title": "pip-release.yml tombstone smoke-test must cd out of repo root before importing",
"files": [".github/workflows/pip-release.yml"],
"require": [
"cd /tmp # away from the repo root's stray wifi_densepose/"
],
"rationale": "ADR-117 §P5 — the repo root contains a legacy `./wifi_densepose/__init__.py` from v1. Python places cwd at sys.path[0], so running `import wifi_densepose` from the repo root after a fresh venv install resolves to the legacy directory and bypasses the tombstone wheel entirely. The smoke-test step MUST `cd /tmp` before the import, otherwise CI silently passes against the wrong package. This was the root cause of run 26366648768.",
"ref": "https://github.com/ruvnet/RuView/pull/786"
},
{
"id": "RuView#786-pypi-token-auth",
"title": "pip-release.yml must authenticate to PyPI via PYPI_API_TOKEN secret, not OIDC",
"files": [".github/workflows/pip-release.yml"],
"require": [
"password: ${{ secrets.PYPI_API_TOKEN }}"
],
"forbid": [
"id-token: write"
],
"rationale": "ADR-117 §P5 — the project is registered with PyPI via API token, not OIDC Trusted Publisher. The token is sourced from GCP Secret Manager (see docs/integrations/pypi-release.md). Re-introducing the `id-token: write` permission would suggest a partial OIDC migration that won't actually work without registering the Trusted Publisher on pypi.org first — a silent regression that would 403 on the next publish.",
"ref": "https://github.com/ruvnet/RuView/pull/786"
}
]
}