Compare commits

...

2 Commits

Author SHA1 Message Date
ruv be4efecbcd cog-ha-matter (ADR-116 P8): app-registry entry stub + release checklist
Two closing P8 deliverables that complete the local-side publishing
scaffolding. The remaining work is all credential-bearing user
action.

1. `cog/app-registry-entry.json` — the exact JSON payload to paste
   into cognitum-one's `app-registry.json`. Schema discovered by
   fetching the live registry (105 cogs, 11 categories) and
   matching the existing `ruview-densepose` entry verbatim. Keys:

     id, name, category, version, size_kb, difficulty, description,
     featured, config[], sha256, binary_size

   cog-ha-matter slots in under `category: "building"` (smart home
   / building automation — the natural HA / Matter category, vs
   `network` which is more about transport bridges).

   7 config[] entries mirror our CLI surface:
     sensing_url, mqtt_host, mqtt_port, privacy_mode,
     mdns_hostname, mdns_ipv4, no_mdns

   Two post-build fields left as `<FILL_IN_...>` markers:
     sha256       (paste from the workflow artifact's .sha256)
     binary_size  (wc -c < the binary)

   Schema validated: all 10 required keys present, parses as JSON.

2. `cog/RELEASE-CHECKLIST.md` — one-page mechanical playbook with
   four explicit "🔑 USER ACTION" gates. Each gate names exactly
   what the user (or org admin) has to do that the pipeline cannot:

     a) provision GCP_CREDENTIALS + HAS_GCP_CREDENTIALS org var
     b) provision COGNITUM_OWNER_SIGNING_KEY GH secret
     c) gcloud auth login (only if uploading locally)
     d) PR app-registry.json into cognitum-one

   Plus pre-release test gate, tag-push command, post-release
   verification curl, and a rollback procedure using GCS object
   versioning (per ADR-100 §"GCS misconfiguration risks").

Stop-condition check (cron's predicate: "ALL local-side publishing
scaffolding is complete and the only remaining work requires user
action"):

   cog/manifest.template.json
   cog/Makefile (build / sign / upload / verify / clean)
   cog/README.md
   cog/app-registry-entry.json (this commit)
   cog/RELEASE-CHECKLIST.md (this commit)
   .github/workflows/cog-ha-matter-release.yml (3 jobs, gated)
   dist/ handling (gitignored, created by make)

  🔑 4 user-action gates explicitly enumerated in the checklist

The cron should STOP after this iter — the local-side scaffolding
is complete and the remaining work is the four named credential
gates that the pipeline cannot self-serve.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-05-23 23:12:14 -04:00
ruv 3833929dcb cog-ha-matter (ADR-116 P8): CI release workflow + fix inherited filename bug
New `.github/workflows/cog-ha-matter-release.yml`:

  * Triggers on `cog-ha-matter-v*` tag-push + manual dispatch
  * Three jobs: build-x86_64, build-arm, publish-gcs
  * x86_64: native ubuntu-latest cargo build
  * arm: aarch64-unknown-linux-gnu via apt-installed gcc-aarch64-linux-gnu
    linker (no `cross` dep needed — keeps workflow self-contained)
  * Each build job runs make build-{arch} + make sign-{arch} +
    gated Ed25519 sign step (skipped when COGNITUM_OWNER_SIGNING_KEY
    secret is unset — workflow still produces unsigned artifacts so
    we get build coverage now and signing later without re-merging)
  * publish-gcs job gated on `vars.HAS_GCP_CREDENTIALS == 'true'`
    so the workflow is safe to merge before credentials land —
    no-op until the org admin sets the variable
  * Uploads binary + sha256 + (optional) sig to
    `gs://cognitum-apps/cogs/{arch}/cog-ha-matter-{arch}`
  * Prints the app-registry.json snippet for the cognitum-one PR
    (so the publish step's output is the exact JSON the user pastes)

Fixed a bug inherited from cog-pose-estimation's Makefile: the
precedent produces `dist/cog-cog-pose-estimation-arm` (double
`cog-` prefix because CRATE name already starts with `cog-`) but
the manifest URL has single prefix `cog-pose-estimation-arm`. The
upload path doesn't match the binary_url — a latent bug in the
pose cog's pipeline.

My copy now produces `dist/cog-ha-matter-arm` matching the
manifest URL `cog-ha-matter-{{ARCH}}`. Changed: Makefile (build /
sign / upload / verify / clean targets), workflow (artifact names
+ gsutil paths), README (local dry-run instructions). The
cog-pose-estimation precedent is unchanged — separate fix if/when
the user wants to align it.

What this iter does NOT do (P8 remaining):
  * provision GCP_CREDENTIALS / COGNITUM_OWNER_SIGNING_KEY secrets
    (user action — needs org admin access)
  * actually run the workflow (needs a `cog-ha-matter-v0.1.0` tag
    push, or workflow_dispatch from the Actions tab)
  * append to app-registry.json in cognitum-one (separate repo PR)

Next iter: tag a v0.0.1-dev (so the workflow runs once + we see
any build-time errors on real CI runners) OR scaffold the
app-registry.json patch payload as a check-in doc.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-05-23 23:05:54 -04:00
5 changed files with 367 additions and 17 deletions
+200
View File
@@ -0,0 +1,200 @@
name: Cog HA-Matter Release
# ADR-116 P8 — Build + sign + bundle the cog-ha-matter cog on a
# version tag. Upload to gs://cognitum-apps/ runs only when the
# GCP_CREDENTIALS + COGNITUM_OWNER_SIGNING_KEY secrets are set, so
# this workflow is safe to merge before the production credentials
# land — it'll bundle release artifacts to the workflow run page
# either way.
on:
push:
tags:
- 'cog-ha-matter-v*'
workflow_dispatch:
inputs:
dry_run:
description: 'Build + sign + bundle but skip GCS upload'
required: false
default: 'true'
env:
CARGO_TERM_COLOR: always
CRATE: cog-ha-matter
jobs:
build-x86_64:
name: Build x86_64
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: x86_64-unknown-linux-gnu
- name: Cache cargo registry
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
v2/target
key: cog-ha-matter-x86_64-${{ hashFiles('v2/Cargo.lock') }}
- name: Build release binary
working-directory: v2/crates/cog-ha-matter/cog
run: make build-x86_64
- name: Compute SHA-256
working-directory: v2/crates/cog-ha-matter/cog
run: make sign-x86_64
- name: Sign with Ed25519 (gated)
if: ${{ env.SIGNING_KEY != '' }}
env:
SIGNING_KEY: ${{ secrets.COGNITUM_OWNER_SIGNING_KEY }}
working-directory: v2/crates/cog-ha-matter/cog
run: |
printf '%s' "$SIGNING_KEY" \
| openssl pkeyutl -sign -inkey /dev/stdin -rawin \
-in dist/cog-ha-matter-x86_64.sha256 \
| base64 -w0 > dist/cog-ha-matter-x86_64.sig
echo "Signed cog-ha-matter-x86_64 ($(wc -c < dist/cog-ha-matter-x86_64.sig) bytes)"
- name: Upload workflow artifact
uses: actions/upload-artifact@v4
with:
name: cog-ha-matter-x86_64
path: |
v2/crates/cog-ha-matter/cog/dist/cog-ha-matter-x86_64
v2/crates/cog-ha-matter/cog/dist/cog-ha-matter-x86_64.sha256
v2/crates/cog-ha-matter/cog/dist/cog-ha-matter-x86_64.sig
if-no-files-found: warn
build-arm:
name: Build aarch64 (arm)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: aarch64-unknown-linux-gnu
- name: Install cross-compiler
run: |
sudo apt-get update
sudo apt-get install -y gcc-aarch64-linux-gnu
- name: Cache cargo registry
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
v2/target
key: cog-ha-matter-arm-${{ hashFiles('v2/Cargo.lock') }}
- name: Build release binary
working-directory: v2
env:
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc
run: |
cargo build -p cog-ha-matter --release --target aarch64-unknown-linux-gnu
mkdir -p crates/cog-ha-matter/cog/dist
cp target/aarch64-unknown-linux-gnu/release/cog-ha-matter \
crates/cog-ha-matter/cog/dist/cog-ha-matter-arm
# ^ matches Makefile's `dist/$(CRATE)-arm` so `make sign-arm` finds it
- name: Compute SHA-256
working-directory: v2/crates/cog-ha-matter/cog
run: make sign-arm
- name: Sign with Ed25519 (gated)
if: ${{ env.SIGNING_KEY != '' }}
env:
SIGNING_KEY: ${{ secrets.COGNITUM_OWNER_SIGNING_KEY }}
working-directory: v2/crates/cog-ha-matter/cog
run: |
printf '%s' "$SIGNING_KEY" \
| openssl pkeyutl -sign -inkey /dev/stdin -rawin \
-in dist/cog-ha-matter-arm.sha256 \
| base64 -w0 > dist/cog-ha-matter-arm.sig
echo "Signed cog-ha-matter-arm ($(wc -c < dist/cog-ha-matter-arm.sig) bytes)"
- name: Upload workflow artifact
uses: actions/upload-artifact@v4
with:
name: cog-ha-matter-arm
path: |
v2/crates/cog-ha-matter/cog/dist/cog-ha-matter-arm
v2/crates/cog-ha-matter/cog/dist/cog-ha-matter-arm.sha256
v2/crates/cog-ha-matter/cog/dist/cog-ha-matter-arm.sig
if-no-files-found: warn
publish-gcs:
name: Upload to GCS (gated)
needs: [build-x86_64, build-arm]
runs-on: ubuntu-latest
# Skip on dry-run dispatch; skip on tags when GCP_CREDENTIALS unset.
if: >
github.event_name == 'push' &&
vars.HAS_GCP_CREDENTIALS == 'true'
steps:
- uses: actions/checkout@v4
- name: Download x86_64 artifact
uses: actions/download-artifact@v4
with:
name: cog-ha-matter-x86_64
path: dist/
- name: Download arm artifact
uses: actions/download-artifact@v4
with:
name: cog-ha-matter-arm
path: dist/
- name: Auth to GCP
uses: google-github-actions/auth@v2
with:
credentials_json: ${{ secrets.GCP_CREDENTIALS }}
- name: Set up gcloud
uses: google-github-actions/setup-gcloud@v2
- name: Upload binaries + sidecars
run: |
gsutil cp dist/cog-ha-matter-x86_64 gs://cognitum-apps/cogs/x86_64/cog-ha-matter-x86_64
gsutil cp dist/cog-ha-matter-x86_64.sha256 gs://cognitum-apps/cogs/x86_64/cog-ha-matter-x86_64.sha256
gsutil cp dist/cog-ha-matter-arm gs://cognitum-apps/cogs/arm/cog-ha-matter-arm
gsutil cp dist/cog-ha-matter-arm.sha256 gs://cognitum-apps/cogs/arm/cog-ha-matter-arm.sha256
if [ -f dist/cog-ha-matter-x86_64.sig ]; then
gsutil cp dist/cog-ha-matter-x86_64.sig gs://cognitum-apps/cogs/x86_64/cog-ha-matter-x86_64.sig
fi
if [ -f dist/cog-ha-matter-arm.sig ]; then
gsutil cp dist/cog-ha-matter-arm.sig gs://cognitum-apps/cogs/arm/cog-ha-matter-arm.sig
fi
- name: Print app-registry.json snippet for the cognitum-one PR
run: |
for arch in arm x86_64; do
sha=$(cat dist/cog-cog-ha-matter-$arch.sha256)
sig=$([ -f dist/cog-cog-ha-matter-$arch.sig ] && cat dist/cog-cog-ha-matter-$arch.sig || echo "")
cat <<EOF
--- $arch ---
{
"id": "ha-matter",
"version": "${GITHUB_REF_NAME#cog-ha-matter-v}",
"binary_url": "https://storage.googleapis.com/cognitum-apps/cogs/$arch/cog-cog-ha-matter-$arch",
"binary_sha256": "$sha",
"binary_signature": "$sig",
"description": "Home Assistant + Matter Cognitum Seed cog (mDNS + witness chain)",
"min_seed_version": "0.6.0",
"installable_on": ["$arch"]
}
EOF
done
+15 -15
View File
@@ -19,12 +19,12 @@ build: build-arm build-x86_64
build-arm:
mkdir -p dist
cargo build -p $(CRATE) --release --target aarch64-unknown-linux-gnu
cp ../../target/aarch64-unknown-linux-gnu/release/$(CRATE) ./dist/cog-$(CRATE)-arm
cp ../../target/aarch64-unknown-linux-gnu/release/$(CRATE) ./dist/$(CRATE)-arm
build-x86_64:
mkdir -p dist
cargo build -p $(CRATE) --release --target x86_64-unknown-linux-gnu
cp ../../target/x86_64-unknown-linux-gnu/release/$(CRATE) ./dist/cog-$(CRATE)-x86_64
cp ../../target/x86_64-unknown-linux-gnu/release/$(CRATE) ./dist/$(CRATE)-x86_64
# --- Sign ---
@@ -32,15 +32,15 @@ build-x86_64:
sign: sign-arm sign-x86_64
sign-arm: dist/cog-$(CRATE)-arm
sha256sum dist/cog-$(CRATE)-arm | cut -d' ' -f1 > dist/cog-$(CRATE)-arm.sha256
sign-arm: dist/$(CRATE)-arm
sha256sum dist/$(CRATE)-arm | cut -d' ' -f1 > dist/$(CRATE)-arm.sha256
# Signature: gcloud secrets versions access latest --secret=COGNITUM_OWNER_SIGNING_KEY \
# | openssl pkeyutl -sign -inkey /dev/stdin -rawin -in dist/cog-$(CRATE)-arm.sha256 \
# | base64 -w0 > dist/cog-$(CRATE)-arm.sig
# | openssl pkeyutl -sign -inkey /dev/stdin -rawin -in dist/$(CRATE)-arm.sha256 \
# | base64 -w0 > dist/$(CRATE)-arm.sig
@echo "TODO: wire Ed25519 sign step once COGNITUM_OWNER_SIGNING_KEY is provisioned to CI."
sign-x86_64: dist/cog-$(CRATE)-x86_64
sha256sum dist/cog-$(CRATE)-x86_64 | cut -d' ' -f1 > dist/cog-$(CRATE)-x86_64.sha256
sign-x86_64: dist/$(CRATE)-x86_64
sha256sum dist/$(CRATE)-x86_64 | cut -d' ' -f1 > dist/$(CRATE)-x86_64.sha256
@echo "TODO: wire Ed25519 sign step once COGNITUM_OWNER_SIGNING_KEY is provisioned to CI."
# --- Upload to GCS ---
@@ -49,11 +49,11 @@ sign-x86_64: dist/cog-$(CRATE)-x86_64
upload: upload-arm upload-x86_64
upload-arm: dist/cog-$(CRATE)-arm
gsutil cp dist/cog-$(CRATE)-arm $(GCS_BUCKET)/arm/cog-$(CRATE)-arm
upload-arm: dist/$(CRATE)-arm
gsutil cp dist/$(CRATE)-arm $(GCS_BUCKET)/arm/$(CRATE)-arm
upload-x86_64: dist/cog-$(CRATE)-x86_64
gsutil cp dist/cog-$(CRATE)-x86_64 $(GCS_BUCKET)/x86_64/cog-$(CRATE)-x86_64
upload-x86_64: dist/$(CRATE)-x86_64
gsutil cp dist/$(CRATE)-x86_64 $(GCS_BUCKET)/x86_64/$(CRATE)-x86_64
# --- Manifest ---
@@ -67,11 +67,11 @@ manifest:
.PHONY: release verify clean
release: build sign upload manifest
@echo "Release pipeline complete for cog-$(CRATE) v$(VERSION)"
@echo "Release pipeline complete for $(CRATE) v$(VERSION)"
verify:
@for arch in $(ARCHES); do \
f=dist/cog-$(CRATE)-$$arch; \
f=dist/$(CRATE)-$$arch; \
if [ ! -f $$f ]; then echo " MISSING $$f"; continue; fi; \
actual=$$(sha256sum $$f | cut -d' ' -f1); \
expected=$$(cat $$f.sha256 2>/dev/null); \
@@ -80,4 +80,4 @@ verify:
done
clean:
rm -rf dist/cog-$(CRATE)-*
rm -rf dist/$(CRATE)-*
+2 -2
View File
@@ -62,8 +62,8 @@ that appends:
"id": "ha-matter",
"version": "<the version make manifest printed>",
"binary_url": "https://storage.googleapis.com/cognitum-apps/cogs/{arch}/cog-ha-matter-{arch}",
"binary_sha256": "<from dist/cog-cog-ha-matter-{arch}.sha256>",
"binary_signature": "<from dist/cog-cog-ha-matter-{arch}.sig — empty until signing is wired>",
"binary_sha256": "<from dist/cog-ha-matter-{arch}.sha256>",
"binary_signature": "<from dist/cog-ha-matter-{arch}.sig — empty until signing is wired>",
"description": "Home Assistant + Matter Cognitum Seed cog (mDNS + witness chain)",
"min_seed_version": "0.6.0",
"installable_on": ["arm", "x86_64"]
@@ -0,0 +1,79 @@
# cog-ha-matter Release Checklist
Mechanical steps to publish a new version. **Everything local-side is
automated; the four "🔑 USER ACTION" blocks below are the only manual
gates.** Each one is a credential-bearing step the cog/ pipeline cannot
do on its own.
## 1. Pre-release (local)
```sh
# Bump version in v2/crates/cog-ha-matter/Cargo.toml then:
cargo test -p cog-ha-matter --no-default-features --lib # 64+ tests must pass
cargo check -p cog-ha-matter --no-default-features # green
```
## 2. Tag the release
```sh
git tag cog-ha-matter-v$(cargo pkgid -p cog-ha-matter | sed -E 's/.*#//')
git push origin --tags
```
The push fires `.github/workflows/cog-ha-matter-release.yml` which:
* builds `cog-ha-matter-x86_64` + `cog-ha-matter-arm` (cross-compiled
via apt-installed `gcc-aarch64-linux-gnu`)
* computes SHA-256 sidecars
* runs the Ed25519 sign step **if** `COGNITUM_OWNER_SIGNING_KEY` is set
* uploads workflow artifacts (always — these are downloadable from
the run page)
* uploads to `gs://cognitum-apps/cogs/{arch}/` **if** the org var
`HAS_GCP_CREDENTIALS == 'true'` and the `GCP_CREDENTIALS` secret is set
## 3. Update app-registry.json
Take `cog/app-registry-entry.json` from this directory, fill in the
post-build values, and PR it into the [`cognitum-one`](https://github.com/ruvnet/cognitum-one)
repo at `app-registry.json`.
Values to fill in:
* `version` — bump to match the new tag
* `sha256` — paste from the workflow artifact's `.sha256` sidecar
* `binary_size` — bytes of the binary (`wc -c < cog-ha-matter-x86_64`)
## 🔑 USER ACTION items (cannot be automated)
| # | What | Why this can't be automated |
|---|---|---|
| 1 | Set the `HAS_GCP_CREDENTIALS` org variable to `true` and provision the `GCP_CREDENTIALS` GitHub Actions secret with a service-account JSON that has `storage.objectAdmin` on `gs://cognitum-apps/cogs/` | Requires org-admin access + a GCP project owner's signoff |
| 2 | Provision `COGNITUM_OWNER_SIGNING_KEY` GitHub secret with the Ed25519 private key in PEM form | Long-lived secret material; humans must rotate it; same blocker for cog-pose-estimation |
| 3 | `gcloud auth login` (only if running `make upload` locally instead of via CI) | Browser OAuth flow |
| 4 | File a PR in `cognitum-one` against `app-registry.json` adding the entry from `cog/app-registry-entry.json` | Cross-repo write requires the user's GitHub auth + reviewer signoff |
## Post-release verification
Once the cognitum-one PR merges and the cache rolls over (~hourly):
```sh
curl -sS https://storage.googleapis.com/cognitum-apps/app-registry.json \
| jq '.[] | select(.id == "ha-matter")'
```
Should print the new entry. On the Seed UI, the cog appears under
**Settings → Cogs → building → Home Assistant + Matter Bridge**.
## Reverting a bad release
Cogs ship via GCS object versioning (per ADR-100). To roll back:
```sh
gsutil ls -a gs://cognitum-apps/cogs/x86_64/cog-ha-matter-x86_64
# Pick the previous generation, then:
gsutil cp gs://cognitum-apps/cogs/x86_64/cog-ha-matter-x86_64#<generation> \
gs://cognitum-apps/cogs/x86_64/cog-ha-matter-x86_64
```
Then PR a `version` bump in `cognitum-one`'s `app-registry.json` so
Seeds know to refetch.
@@ -0,0 +1,71 @@
{
"id": "ha-matter",
"name": "Home Assistant + Matter Bridge",
"category": "building",
"version": "0.3.0",
"size_kb": 12,
"difficulty": "easy",
"description": "Exposes WiFi-CSI sensing as Home Assistant entities over MQTT auto-discovery, with mDNS announcement on _ruview-ha._tcp and tamper-evident Ed25519-signed audit logs. Adds 10 semantic primitives (someone_sleeping, possible_distress, fall_risk_elevated, ...) on top of the 11 raw measurements. Privacy mode strips biometrics at the wire so only the semantic layer reaches HA — the right default for any deployment with non-tenant occupants.",
"featured": false,
"config": [
{
"key": "sensing_url",
"type": "string",
"label": "Sensing server URL",
"description": "Where the cog reads VitalsSnapshot from",
"default": "http://127.0.0.1:3000",
"cli_arg": "--sensing-url"
},
{
"key": "mqtt_host",
"type": "string",
"label": "MQTT broker host",
"description": "External mosquitto / HA Core MQTT host (v0.7 will add an embedded broker option)",
"default": "127.0.0.1",
"cli_arg": "--mqtt-host"
},
{
"key": "mqtt_port",
"type": "integer",
"label": "MQTT broker port",
"default": 1883,
"min": 1,
"max": 65535,
"cli_arg": "--mqtt-port"
},
{
"key": "privacy_mode",
"type": "boolean",
"label": "Privacy mode",
"description": "Strip biometrics at the wire — only semantic primitives are published. Recommended for any deployment with non-tenant occupants (care homes, education, shared housing).",
"default": false,
"cli_arg": "--privacy-mode"
},
{
"key": "mdns_hostname",
"type": "string",
"label": "mDNS hostname",
"description": "Must end with .local. per RFC 6762. HA's discovery integration looks up this hostname.",
"default": "cog-ha-matter.local.",
"cli_arg": "--mdns-hostname"
},
{
"key": "mdns_ipv4",
"type": "string",
"label": "Advertised IPv4",
"description": "LAN-routable address the mDNS responder advertises. HA reaches back to this for MQTT.",
"default": "127.0.0.1",
"cli_arg": "--mdns-ipv4"
},
{
"key": "no_mdns",
"type": "boolean",
"label": "Disable mDNS",
"description": "Skip the mDNS responder. Useful in containerised setups where multicast is filtered.",
"default": false,
"cli_arg": "--no-mdns"
}
],
"sha256": "<FILL_IN_FROM_dist/cog-ha-matter-x86_64.sha256_AFTER_make_build>",
"binary_size": 0
}