Commit graph

265 commits

Author SHA1 Message Date
rcourtman
1ba7bb06a0 Verify public Helm chart publication 2026-07-05 09:40:20 +01:00
rcourtman
d029e4dc42 Fix demo fixture entitlement recovery
Seed the hidden demo fixture entitlement during stable demo updates so release builds can enable governed mock resources after runtime configuration is restored.

Keep the deployment contract and release policy checks aligned with the release-build entitlement gate.
2026-07-04 22:44:43 +01:00
rcourtman
cd6b250ae6 Fix demo verification and agent update recovery
Some checks are pending
Build and Test / Secret Scan (push) Waiting to run
Build and Test / Frontend & Backend (push) Waiting to run
Refs #1515

- restore demo runtime env and verify mock fixtures even when the target version is already installed
- require recovered agent update state to include both URL and token before reporting success
2026-07-04 22:30:58 +01:00
rcourtman
4fcb90673b Allow stable patch artifact publishing
Reuse shared release-line validation for Docker, floating-tag, and Helm artifact workflows so stable patch tags can publish from the previous stable tag without a fabricated same-version RC.
2026-07-04 20:49:47 +01:00
rcourtman
de0c5ff5c1 Retire v6 preview demo target after GA 2026-07-04 16:16:57 +01:00
rcourtman
72c8d9677a Prune nested demo runtime stores 2026-07-04 15:47:00 +01:00
rcourtman
d2ab2b793d Prune demo volatile stores for release deploy 2026-07-04 15:39:35 +01:00
rcourtman
d8487b568a Tighten demo backup cleanup headroom 2026-07-04 15:34:08 +01:00
rcourtman
dc5f0a35c0 Harden installer and demo backup handling 2026-07-04 15:28:42 +01:00
rcourtman
c04ac2f7cf Make private Pro publication block v6 releases
Some checks failed
Build and Test / Secret Scan (push) Has been cancelled
Build and Test / Frontend & Backend (push) Has been cancelled
2026-06-15 15:37:13 +01:00
rcourtman
377fd5131d Harden release integration bootstrap gate 2026-06-14 22:26:13 +01:00
rcourtman
6169f3cea5 Bound Go benchmark release gate 2026-06-14 20:47:23 +01:00
rcourtman
50cbc27b71 Refresh workflow action pins 2026-06-14 19:26:21 +01:00
rcourtman
ba8cfc8229 Harden release gate workflow 2026-06-14 19:19:39 +01:00
rcourtman
cbb3b42ce0 Cancel superseded Build and Test runs
Refs CI notification noise on pulse/v6-release.
2026-06-11 16:32:09 +01:00
rcourtman
b0f22f1130 Stop posting retest boilerplate on maintainer issue reopens
The retest-comment workflow fired on 'reopened' as well as 'opened', and
canPostRetestComment gated on the issue author's association, not the
reopen actor's. Since non-collaborator reporters cannot reopen
maintainer-closed issues, a reopened event is in practice always a
deliberate maintainer decision, and the bot would post version-retest
boilerplate that contradicts the maintainer's own comment while planting
the needs-retest auto-close marker on an issue they chose to keep open
(seen on #1471).

Restrict the trigger and the guard to 'opened' and pin the reopened skip
with a test.
2026-06-11 15:10:33 +01:00
rcourtman
a405bfc7e8 test(go): stamp parity storage fixture LastSeen; raise CI go test timeout
Two failures surfaced once CI reached the Go steps for the first time
in ~100 runs:

- TestParityStorageFields predates models.Storage.LastSeen (e2a036ce2,
  zero = never seen, no fabricated stamps), so the fixture ingested as
  never-seen and LastSeen() was honestly zero. Stamp the fixture like
  the real pollers do and assert exact round-trip, matching the docker
  host parity test.
- internal/api alone takes ~10m under -race on a fast machine, so the
  10m per-binary budget set when the package was smaller times out on
  CI runners. Raise to 25m. The package has no sleep-debt (all test
  sleeps are milliseconds); it is legitimately heavy.
2026-06-11 12:02:29 +01:00
rcourtman
edd7f001ab Restore pulse:test image build in the e2e workflow
Some checks are pending
Build and Test / Secret Scan (push) Waiting to run
Build and Test / Frontend & Backend (push) Waiting to run
The Core E2E workflow on this branch lost the 'docker build -t
pulse:test --target runtime .' line that release/5.1 still has, so
every dispatched run failed at compose-up with 'pull access denied for
pulse'. Masked until now because the workflow only auto-triggers on
main.
2026-06-10 12:10:32 +01:00
rcourtman
bd6f77e093 Prepare v6.0.0 release candidate
Tighten v5-to-v6 upgrade safety, release installability, provider MSP mode handling, AI cost accounting, metrics flushing, and frontend guardrails for the v6.0.0 GA candidate.
2026-06-04 14:07:14 +01:00
rcourtman
0d0eb4bf11 Stabilize v6 release dry-run backend gate 2026-06-03 18:12:42 +01:00
rcourtman
42fb8eed3f Publish provider MSP control-plane image 2026-06-02 15:51:21 +01:00
rcourtman
52fd847ac6 Let install.sh smoke harness opt past Docker-environment refusal
Some checks are pending
Build and Test / Secret Scan (push) Waiting to run
Build and Test / Frontend & Backend (push) Waiting to run
install.sh refuses to run inside Docker (correct behavior for end
users), but the install-sh-smoke gate runs the documented systemd
install path inside a privileged systemd-in-Docker container — the
one legitimate bypass case. Added PULSE_INSTALL_ALLOW_DOCKER=1
escape hatch in check_docker_environment() and set it on the docker
exec in the smoke workflow. Takes effect on the next RC; v6.0.0-rc.6's
published install.sh predates this and is signed-frozen, so its
post-publish smoke gate will remain red until the next prerelease.
2026-05-27 22:12:17 +01:00
rcourtman
7c35977fcb Fix post-publish pipeline failures for v6.0.0-rc.6
promote-floating-tags.yml waited on rcourtman/pulse-agent:${TAG} and
promoted floating tags for it, but publish-docker.yml never pushes the
agent image — Pulse Agent ships as GitHub Release binaries
(publish-docker.yml line 199 confirms). The wait timed out after 5
minutes on every release since the pulse-agent push step was removed,
leaving floating tags unpromoted. Stripped the vestigial wait block,
the agent promote step, and the agent line in the summary. Updated
header comments in both files to drop the past-tense reference.

install-sh-smoke.yml booted jrei/systemd-debian:12 without
--cgroupns=host, so on GHA ubuntu-24.04 (cgroup v2 unified hierarchy)
the container's systemd PID 1 exited before mounting the cgroup tree
and the container disappeared during readiness polling. Added
--cgroupns=host, an explicit /run/lock tmpfs, and dropped --rm so the
container persists for diagnostic capture on failure (trap handles
cleanup). Added an "is container still running" probe inside the
readiness loop and richer diagnostic output on timeout.
2026-05-27 22:08:15 +01:00
rcourtman
1dfc4ee6d3 Require .sshsig sidecar for install.sh --archive path
The --archive PATH flag (and the Proxmox LXC bootstrap which
propagates --archive into the container) reached install_pulse_archive
without going through download_release_archive, which is the only
place that previously verified the cryptographic signature on a
Pulse release tarball. That made --archive a clean signature-
verification bypass: anyone who could swap a local tarball
between download and install (shared /tmp on a Proxmox host, a
maintainer testing a swapped local file, etc.) would get
arbitrary root code execution at extract time.

install.sh:
- install_pulse_archive now requires ${archive_path}.sshsig and
  verifies it via the existing verify_release_signature helper
  before tar runs. Missing or invalid signature is a hard
  fail-closed.
- download_release_archive now keeps the .sshsig alongside the
  archive at ${archive_path}.sshsig instead of stashing it in a
  mktemp file that got deleted post-verify. install_pulse_archive
  re-verifies it; no behavior change for the curl|bash path beyond
  the extra integrity check at extract time.
- Proxmox LXC bootstrap now pct push'es ${container_archive_source}.sshsig
  alongside the archive into the container so the in-container
  install_pulse_archive run can verify. Missing sidecar fails the
  bootstrap (cleanup_on_error tears down the half-provisioned
  container).
- Cleanup paths that remove the temp archive on success/failure
  now also remove the sidecar.
- --help advertises the .sshsig requirement.

install-sh-smoke.yml:
- Smoke test now pulls ${tarball}.sshsig alongside the tarball
  before running install.sh --archive inside the test container.

Trade-off: maintainer flows that did `install.sh --archive
/path/to/local.tar.gz` against an unsigned local tarball will now
fail with a clear "Required signature sidecar not found" error.
Maintainers can either pull the corresponding .sshsig from the
release alongside the tarball, or skip --archive in favour of the
canonical download path.
2026-05-21 16:13:57 +01:00
rcourtman
2305131d8a Move eval-model-matrix off self-hosted runner
Self-hosted runners attached to a public repo are a known
severe-risk pattern: any workflow that gains the ability to run
PR-controlled code escalates to RCE on the runner host. Even
though this workflow is workflow_dispatch only today, the runner
itself sits one workflow edit away from arbitrary code execution
on whatever machine it's pinned to.

Changes:

- runs-on: self-hosted -> ubuntu-24.04. Reachability of base_url
  from the GitHub-hosted runner is now the dispatcher's
  responsibility. Add tailscale/* steps (like deploy-v6-preview-
  demo.yml or deploy-demo-server.yml already do) if pointing at
  a private Pulse instance.

- Drop the admin/admin fallback on PULSE_EVAL_USER and
  PULSE_EVAL_PASS. The previous default was
  ${{ secrets.PULSE_EVAL_USER || 'admin' }} which silently ran
  the eval as admin/admin if the secrets were missing. Added a
  fail-closed precheck that refuses to proceed unless both
  secrets are set.

- Route inputs.scenario, inputs.models, inputs.providers, and
  inputs.base_url through env: indirection instead of inline
  ${{ }} interpolation into shell. Defense-in-depth against
  script injection from the dispatch payload.

Net result: kills the audit's only CRITICAL finding and the
companion MEDIUM (admin/admin fallback). Eval can still run
exactly as before once secrets are set and base_url is reachable.
2026-05-21 16:09:09 +01:00
rcourtman
2c51890d01 Harden installer extraction and demo workflow inputs
install.sh:
- find_pulse_binary_in_dir now rejects symlinks, so a malicious
  tarball that smuggled a symlink at the expected binary path
  cannot redirect the cp to $INSTALL_DIR/bin/pulse onto an
  arbitrary file outside the extract dir.
- tar -xzf calls in install_pulse_archive now pass
  --no-same-owner --no-overwrite-dir, defending against archive
  entries that try to chown extracted files or rewrite existing
  directory metadata.

Demo deploy workflows:
- deploy-demo-server.yml routes inputs.target through env: so the
  workflow_dispatch input cannot break out of the case statement,
  even though type: choice constrains it client-side.
- update-demo-server.yml routes inputs.tag, inputs.target, and
  github.event.release.tag_name through env: for the same reason.

All defense-in-depth: download_release_archive still signature-
verifies the tarball, and the demo workflows are gated to write-
access dispatchers. The argument-vs-env distinction matters once
the trigger surface or trust boundary changes.
2026-05-21 15:04:35 +01:00
rcourtman
adb0f483bf Route GITHUB_TOKEN through env in validate-release-assets
Five steps in the release-asset validation workflow built curl
commands like:

    curl -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" ...

That renders the token into the step's shell command line, where it
is visible to any process able to read /proc on the runner during
the curl call. Convert all nine such call sites to pass the token
via env: GH_TOKEN and reference $GH_TOKEN in the header instead, so
the token only lives in the step env, never on argv.

No behavior change; the token value is identical.
2026-05-21 12:28:43 +01:00
rcourtman
93c62e691a Aggregate simplify-review cleanups (no behavior change)
Some checks failed
Build and Test / Secret Scan (push) Has been cancelled
Build and Test / Frontend & Backend (push) Has been cancelled
Six small refactors aggregated from a simplify-review pass over this
session's commits:

1. internal/config/persistence_relay.go — LoadRelayConfig had two
   ApplyEnvOverrides call sites (one inside the not-exist branch, one
   on the happy path) and a redundant cfg = DefaultConfig() reassignment.
   Collapse to a single ApplyEnvOverrides call after the load attempt;
   the file-absent branch already has the default cfg from line 1.

2. internal/relay/config_env.go — swap two strings.TrimSpace(os.Getenv(...))
   calls for utils.GetenvTrim, matching the 30+ existing call sites in
   internal/config/config.go. Trim narrating comments back to the
   product-behavior sentences that aren't obvious from the code.

3. internal/relay/config_env_test.go — collapse seven near-identical
   ApplyEnvOverrides scenarios into a single table-driven test
   (TestApplyEnvOverridesTable). Reduces ~85 lines to ~60 and gives each
   subcase a named t.Run for clearer failure output. Keeps the
   nil-config-safe and parseEnvBool tests separate since they exercise
   different surfaces.

4. .github/workflows/install-sh-smoke.yml — replace the /api/health
   bash for-loop (sleep 2; curl; loop 30x) with a single
   curl --retry 30 --retry-delay 2 --retry-connrefused --retry-all-errors
   invocation. Curl already implements the same polling behaviour
   natively; the bash loop was 13 lines of redundant scaffolding.

5. scripts/installtests/build_release_assets_test.go — extract the
   repeated "read file, iterate required substrings, fail on first
   miss" boilerplate into assertFileContainsAll(t, path, required...).
   Migrate the four tests I added in this session; existing tests in
   the file follow the same shape and can adopt the helper
   incrementally without churning unrelated code in this commit. Also
   updated the pinned curl string for the /api/health retry change.

Contract-neutral: every change preserves identical user-visible
behavior. PULSE_ALLOW_CONTRACT_NEUTRAL_COMMIT applied for the
canonical-shape-guard bypass; sensitivity, gitleaks, governance-stage,
control-plane, status, registry, contract, and pre-commit hooks still
run.

Verified locally:
- go test ./internal/relay/ ./internal/config/ → all pass
- go test ./scripts/installtests/ → all pass
- ruby -ryaml install-sh-smoke.yml → parses clean
2026-05-12 17:32:11 +01:00
rcourtman
22a94f47d9 Skip release publish downstreams for drafts 2026-05-12 17:32:11 +01:00
rcourtman
29a815ef2a Fail closed on stale API action plans 2026-05-12 16:55:51 +01:00
rcourtman
3566a4d61d Drive promote-floating-tags via workflow_call from create-release
promote-floating-tags.yml's `workflow_run` chain off publish-docker.yml
silently stopped firing for rc.3 → rc.5 because publish-docker failed at
the now-removed pulse-agent push step. Customers pulling
rcourtman/pulse:latest, :6, or :6.0 stayed on whatever the previous
successful release had tagged — there was no warning anywhere that the
floating tags were stale.

Same fix pattern as install-sh-smoke (commit 7c0f65425) and
publish-helm-chart (commit 14c79a28e): add a workflow_call trigger to
promote-floating-tags.yml and call it explicitly from create-release.yml
after validate_release_assets succeeds.

Gating on validate_release_assets is intentional: that workflow waits
for the docker image to be pullable from the registry (with retry
backoff), so by the time it succeeds the image manifest exists and
re-tagging it to latest/major/minor cannot point at vapor.

The legacy workflow_run trigger stays as the primary path; this just
guarantees promotion even when the chain doesn't fire.

Tag-resolver step now accepts inputs from workflow_call / workflow_dispatch
and only falls back to the workflow_run derivation when inputs are absent,
so all three entry paths converge on the same identity.

Pinned in build_release_assets_test.go:
- new TestPromoteFloatingTagsReachableViaWorkflowCall pins the trigger
  declaration and the input-priority resolver
- existing TestCreateReleaseUploadsPowerShellInstaller extended to pin
  the promote_floating_tags job wiring (uses, tag, prerelease)

Contract delta in deployment-installability.md Extension Point 7
documents the same explicit-workflow_call requirement that applies to
publish-helm-chart, extended to promote-floating-tags.
2026-05-12 16:47:51 +01:00
rcourtman
14c79a28e7 Trigger publish-helm-chart via workflow_call from create-release
v6 rc.1 → rc.5 published successfully but the Helm chart never landed on
rcourtman.github.io/Pulse/index.yaml — the index still ends at v5.1.30.
`helm install pulse pulse/pulse --version 6.0.0-rc.5` returns
chart-not-found; without `--version` helm pulls the latest published
chart (v5.1.30) into a customer's v6 cluster.

Root cause: GitHub does not fire `release: published` for releases that
were created as drafts and later PATCHed to draft=false. create-release.yml
deliberately uses that path so it can upload assets and run
validate-release-assets against the draft before promoting. Inspection of
the workflow run history confirms: every gh-API `release: published` event
since 2026-03-02 has been from manually-dispatched v5 stable cuts; zero
fired for v6 RCs published through the create-release pipeline.

Fix the same way install-sh-smoke was wired in commit 7c0f65425: add a
`workflow_call` trigger to publish-helm-chart.yml and call it explicitly
from create-release.yml as a downstream of validate_release_assets. The
chart-version resolver in publish-helm-chart now accepts inputs from
either workflow_call or workflow_dispatch and only falls back to the
release-event tag when no inputs are present, keeping the legacy
release-event path working for forks / manual gh-CLI publishes that
create with draft=false from the start.

Pinned in build_release_assets_test.go:
- create-release.yml wiring (publish_helm_chart job, version inputs)
- publish-helm-chart.yml workflow_call trigger declaration
- chart-version resolver's input-priority logic

Contract delta in deployment-installability.md Extension Point 7
documents the workflow_call requirement and forbids relying on the
release-published webhook for the create-release.yml draft-promotion
path.

The fix takes effect on the next release through the pipeline. Backfill
of the v6.0.0-rc.5 chart needs a one-time manual dispatch of
publish-helm-chart.yml against chart_version=6.0.0-rc.5.
2026-05-12 16:30:53 +01:00
rcourtman
5ac0484e06 Drop install.sh-smoke push self-test (v5.1.30 fallback was unviable)
Some checks are pending
Build and Test / Secret Scan (push) Waiting to run
Build and Test / Frontend & Backend (push) Waiting to run
Commit 590818744 added a push trigger that re-ran the gate against
v5.1.30 on every workflow edit, aiming to register the workflow for
API dispatch and to validate it before the next release depended on it.
The registration goal was achieved: workflow ID 275278570 is now active
and the gate is dispatchable via `gh workflow run install-sh-smoke.yml`
and the REST API.

The self-test itself was unviable: v5.1.30 doesn't ship
`install.sh.sshsig` (v5 didn't sign installers), so the signature-verify
step 404s on every run. No current published release is a valid known-
good smoke target — rc.5's `install.sh` has the wrong banner / agent
installer (the regression this gate exists to catch), and rc.6 doesn't
exist yet. The push-triggered run would fail forever, drowning real
signals.

Drop the push trigger. The workflow is registered, dispatch is verified
working, and a dispatch against rc.5 just confirmed the gate correctly
fires the banner check ("install.sh banner is not the Pulse server
installer") against the broken release. The first time the gate's
container portion runs end-to-end will be on rc.6 through the
create-release.yml workflow_call. Make the resolve-inputs step a hard
fail if tag or version is empty so any future regression that drops
inputs surfaces explicitly instead of running against a silent fallback.
2026-05-12 11:55:03 +01:00
rcourtman
5908187445 Self-test install.sh smoke gate on every workflow edit against v5.1.30
Commit 7c0f65425 wired install-sh-smoke.yml into create-release.yml but
the workflow has never actually executed — the pre-install structural
checks were validated locally against rc.5, but the privileged systemd
container portion is unproven on GitHub's cgroup-v2 runners. The first
real release through the pipeline would be its trial run, and a bug at
the container layer would block the release.

Add a push-event self-test that re-runs the full gate against v5.1.30
(a known-good release with the same server-installer banner, the same
--version arg handler, and the same ed25519 signing key as v6 RCs)
whenever this workflow file changes on pulse/v6-release or main. This
both validates the gate continuously and registers the workflow with
GitHub's actions/workflows API so it becomes dispatchable via gh CLI
and the REST endpoint — workflows on non-default branches with only
workflow_call + workflow_dispatch never appear in the API until they
have been triggered by a non-dispatch event.

Replace direct `${{ inputs.* }}` references with a single resolve step
that falls back to v5.1.30 / 5.1.30 / github.repository when no inputs
are supplied (push trigger). Drop the now-redundant Resolve release
repository step. Behavior under workflow_call from create-release.yml
is unchanged: the create-release-supplied tag/version/repository win.
2026-05-12 11:50:19 +01:00
rcourtman
7c0f654253 Wire install.sh smoke gate into create-release.yml release pipeline
The smoke gate workflow exists from commit 065ebdb27 but until it is
called from create-release.yml it does not actually protect any release.
That is exactly the regression class that let rc.1 → rc.5 ship with a
broken install.sh: nothing in the release pipeline exercised the
documented secure-install flow against the published GitHub Release URL.

Wire install-sh-smoke.yml as a downstream workflow_call after
validate_release_assets succeeds. Gated on
historical_asset_backfill_only != 'true' since asset-backfill flows
re-upload to an already-published release and the smoke would just
re-confirm what hasn't changed.

Pre-install structural checks were verified locally against rc.5 — the
gate correctly fires the banner / agent-banner / --version handler
assertions against the broken release. The end-to-end container portion
(privileged systemd boot, install.sh execution, /api/health, /api/version
match) will run for the first time on the next release that publishes
through this workflow; existing retry loops on systemd readiness,
service activation, and health endpoint absorb transient runner flakes.

Add install-sh-smoke.yml to the deployment-installability canonical files
and to the release-promotion proof policy's match_files, and add
scripts/installtests/build_release_assets_test.go to that policy's
exact_files (matching the existing pin set for related policies in the
deployment-installability subsystem). Update subsystem_lookup_test.py
fixtures that pinned the exact_files list literally.

Pinned the create-release.yml wiring in build_release_assets_test.go
alongside the validate-release-assets wiring so the smoke step cannot
silently be unwired.

Document the gate's contract responsibilities in
deployment-installability Extension Point 2.
2026-05-12 11:44:04 +01:00
rcourtman
065ebdb276 Add install.sh end-to-end smoke gate against published release
Across v6 rc.1 → rc.5 the published install.sh asset was the agent
installer rather than the server installer, and the README's pinned
ed25519 key did not verify what the pipeline actually signed. The first
broke `bash install.sh --version` and the in-product Update button; the
second silently failed the README's secure-install ssh-keygen step.
Neither was caught by CI because every existing gate operated on the
local release/ build, the Docker image, or the helm chart — nothing
exercised the documented LXC/systemd install commands against the
published release URL.

scripts/validate-release.sh now catches asset-identity drift at build
time. This workflow catches the rest of the regression class — anything
that breaks the actual install at runtime — by running the documented
flow end-to-end against the published release.

What it does:
- Downloads install.sh, install.sh.sshsig, and the linux-amd64 tarball
  from releases/download/<tag>/.
- Extracts the README's pinned pulse-installer ed25519 key and runs the
  exact ssh-keygen -Y verify command from the README's secure-install
  snippet against the downloaded asset.
- Re-checks the server-installer banner, the --version) arg handler, and
  the absence of the agent banner — same pins as validate-release.sh, but
  now against what GitHub is actually serving (not just what was built
  locally).
- Boots jrei/systemd-debian:12 privileged, runs
  `bash install.sh --archive <tarball> --disable-auto-updates` from
  inside, waits for systemd pulse.service to become active, hits
  /api/health, and asserts /api/version reports the expected version.

--archive mode is used rather than --version so the workflow doesn't
depend on install.sh's self-refetch loop (the re-fetched bytes are the
ones we already validated). Auto-updates are disabled to avoid the timer
unit doing anything during the smoke run.

Triggers are workflow_dispatch + workflow_call only. Wire it into
create-release.yml after the next RC validates it green.

Pinned in build_release_assets_test.go so silent deletion or weakening
of any critical assertion (signature verify, banner check, /api/health
hit, version match) trips the test.
2026-05-12 11:25:46 +01:00
rcourtman
920e88ede9 Schedule weekly release-dry-run watchdog against pulse/v6-release
Some checks are pending
Build and Test / Secret Scan (push) Waiting to run
Build and Test / Frontend & Backend (push) Waiting to run
2026-05-11 22:21:59 +01:00
rcourtman
3b2eef4984 Run build-and-test on pulse/v6-release commits to catch fixture/contract drift continuously 2026-05-11 22:19:45 +01:00
rcourtman
e69da0069f Stop pushing pulse-agent Docker image; ship agent only as release-asset binaries 2026-05-11 22:19:09 +01:00
rcourtman
3da835c5bc Publish a distribution path for pulse-mcp
The MCP adapter shipped in slice 51 with one install option:
clone the repo and go build. This slice integrates pulse-mcp
into Pulse's existing governed release pipeline so a Pulse
release publishes a pulse-mcp binary alongside the unified agent
and the install scripts that bring it home in one command.

What ships:

  - scripts/build-release.sh extended to build pulse-mcp for
    the same multi-OS matrix as the unified agent, package
    per-platform tarballs and zips, and copy bare binaries to
    RELEASE_DIR for /releases/latest/download/ redirect
    compatibility.
  - .github/workflows/create-release.yml extended to upload
    the bare pulse-mcp binaries plus install-mcp.sh and
    install-mcp.ps1 as release assets.
  - scripts/install-mcp.sh: bash one-line installer that
    detects platform/arch, downloads the matching binary from
    the configured release (latest by default), verifies SHA256
    against the published checksums.txt, places at
    ~/.local/bin/pulse-mcp (or /usr/local/bin if not writable).
    Honors PULSE_MCP_VERSION, PULSE_MCP_BIN_DIR, PULSE_MCP_REPO,
    PULSE_MCP_NO_VERIFY env vars; declines Windows shells with
    a pointer at the .ps1 sibling.
  - scripts/install-mcp.ps1: PowerShell installer for Windows,
    placing pulse-mcp.exe at $LOCALAPPDATA\pulse-mcp.

Documentation aligned:

  - cmd/pulse-mcp/README.md gains an Install section above
    Quick start with three options: one-line installer,
    GitHub Release download, go install. Documents the macOS
    Gatekeeper bypass since v1 is unnotarized by design.
  - The Settings -> API Access agent-integrations panel now
    surfaces the curl|bash command above the config snippet so
    operators see "install pulse-mcp" before "configure your
    MCP client."
  - docs/releases/AGENT_PARADIGM.md drops the "no published
    distribution path" item from "what it does not do yet" and
    documents the Gatekeeper / Homebrew gaps as next-tier
    follow-ups.

Trade-offs surfaced and chosen:

  - Same cadence as Pulse: pulse-mcp ships per Pulse release,
    not on its own track. The MCP server reads the manifest
    from the Pulse it talks to, so version alignment is the
    natural model.
  - No Homebrew tap or core formula in v1. Maintaining a tap
    is real ongoing work; foundation supports adding Homebrew
    later as a layer.
  - No Docker image. Stdio JSON-RPC fights Docker's stdin
    /stdout pattern.
  - No notarization in v1. SHA256 verification through the
    installer preserves the audit trail; README documents the
    Gatekeeper bypass.

Subsystem contract: deployment-installability.md gains
scripts/install-mcp.sh, scripts/install-mcp.ps1, and
cmd/pulse-mcp/ in canonical files (mid-list entries
renumbered) plus a paragraph documenting the new MCP entry
point alongside the existing installer family.

Verification artifacts:

  - scripts/installtests/build_release_assets_test.go gains
    TestBuildReleasePackagesPulseMcpForAllPlatforms which pins
    the build/package/copy wiring and the load-bearing
    install-mcp.sh helpers (platform detection, SHA256
    verification, install-dir resolution).
  - scripts/release_control/render_release_body_test.py gains
    test_agent_paradigm_release_notes_blurb_documents_-
    distribution_path which pins the AGENT_PARADIGM.md draft's
    install-mcp.sh reference and the four-axis frame so a
    future edit cannot regress the install story silently.

Smoke-tested install-mcp.sh locally on darwin-arm64: platform
detection, install-dir resolution, URL building, and 404 error
handling all correct. The full end-to-end install path becomes
live the moment a Pulse release ships pulse-mcp binaries; the
next RC cut will exercise it.
2026-05-10 17:04:49 +01:00
rcourtman
d6e96ebeca Fix v6 demo release signing key deployment 2026-05-05 21:40:14 +01:00
rcourtman
9ba0c3fa96 Retry release asset uploads 2026-05-03 10:26:51 +01:00
rcourtman
54378a14e5 Fix release validation draft metadata preservation 2026-05-02 02:01:57 +01:00
rcourtman
011d288cb4 Fix release asset validation workflow gates 2026-05-02 00:36:54 +01:00
rcourtman
c8e24f06d7 Fix clean VCS metadata for release builds 2026-05-01 23:12:41 +01:00
rcourtman
4dfb42f877 Port issue-first contribution policy to v6 docs 2026-05-01 20:28:11 +01:00
rcourtman
fb6b53268a Harden release Docker key embedding cache 2026-04-24 17:21:04 +01:00
rcourtman
f58840e8a8 Guard forward release signing against trust-root drift 2026-04-22 19:59:18 +01:00
rcourtman
c0f48b27ba Grant release validation workflow required permissions 2026-04-22 17:47:13 +01:00
rcourtman
9c2e3d5ffb Add historical backfill mode to create-release workflow 2026-04-22 17:43:37 +01:00