Commit Graph

368 Commits (v0.9.64)

Author SHA1 Message Date
boban 9b2d71e1f8 chore: release 0.9.64 — CI fully green (pint style fixes)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 02:30:33 +02:00
boban f82555469a style: pint --test fixes (CI pint step now reachable after the test fix)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 02:29:59 +02:00
boban 770bfec97f chore: release 0.9.63 — green CI (Vite manifest before tests)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 02:26:45 +02:00
boban 25271717c2 fix(ci): build the Vite manifest before php artisan test
Full-page tests render @vite layouts; without public/build/manifest.json (gitignored,
absent on a fresh CI checkout) they 500. Move npm ci + npm run build ahead of the
test step so the manifest exists. Fixes the red CI on every v* tag.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 02:26:20 +02:00
boban bdee34ef8f chore: release 0.9.62 — live release pipeline status
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 02:16:40 +02:00
boban fca75bc0a3 fix(release): neutralise private slug in tests + render step descriptions (review)
- PipelineStatusTest: the private mirror slug 'boksbc/clusev-staging' was hardcoded
  in this tracked test (tests/ ships to the public repo) — exactly the leak the
  design forbids. Neutralise to an example slug 'acme/staging'. Add the missing
  HTTP-error degrade tests (GitHub 5xx, GitHub throwing, test-server unreachable →
  'unknown', never throws) — the core robustness guarantee was untested.
- Live rail: render the per-step description (the built $sub array was dead code)
  and colour the live state by status (online/warning/offline).
- saveTestServer: refresh() the pipeline cache so a changed test-server URL is
  reflected immediately, not after the 15s TTL.
- .env.example: document the dev-only release/pipeline keys (commented, no values).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 02:13:26 +02:00
boban 787e48d8fd feat(release): live pipeline rail + test-server input on the Release page
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 01:55:14 +02:00
boban 0ac819e4ad feat(release): wire the live pipeline + test-server setting into the page
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 01:49:01 +02:00
boban 070c6646f3 feat(release): PipelineStatus — live tag/mirror/CI/test-server status
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 01:43:00 +02:00
boban 06e9fcbad7 chore: remove internal/AI docs from the repo (keep local)
The GitHub-private staging repo is a full push-mirror of Gitea, so export-ignore
(which only protects the public repo via git archive) does not strip these from
the mirror. Untrack the internal/AI-collaboration docs — CLAUDE.md, rules.md,
handoff.md, kickoff-prompt.md and docs/ (specs, plans, runbooks, handoffs) — and
gitignore them so they stay on the dev box but never reach Gitea, the mirror, or
any shipped image. The public repo was already clean via export-ignore.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 01:09:04 +02:00
boban 665c9f93bd chore(release): 0.9.61-beta4 2026-06-23 01:03:27 +02:00
boban 8acc6ab6ba chore(release): 0.9.61-beta3 2026-06-23 00:59:34 +02:00
boban d80e44086a chore(release): 0.9.61-beta2 2026-06-23 00:54:50 +02:00
boban 788d38fab5 chore(release): 0.9.61-beta1 2026-06-23 00:49:06 +02:00
boban 164d17e707 chore: release 0.9.60 — redesigned Release page
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 00:32:44 +02:00
boban 6a284caa44 design(release): rebuild the Release page in the Tactical Terminal system
Replace the bland version + four plain buttons with a composed layout: a current-
version hero (mono version, channel + source), tactile bump tiles showing the
current → target transition with a Beta-tag badge (continue-beta highlighted
accent), styled running/last-pushed states, and a vertical pipeline rail
(Gitea → GitHub-privat → CI → Staging). Tokens-only, responsive 375/768/1280,
German, no emoji; the R5 confirm flow + component contract are unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 00:32:23 +02:00
boban 25516ad323 chore: release 0.9.59 — Phase B1 deploy-to-staging control
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 00:20:24 +02:00
boban d502756b66 fix(release): genuine rollback-path test + install/gitignore hardening (review)
- serve-request.test.sh: the push-fail case pointed origin at a dead https remote,
  so _precheck's fetch failed first and the rollback path in _do_stage was never
  reached (assertions passed trivially). Use a fetch-OK / push-rejecting remote
  (pre-receive exit 1) so the commit+tag are created and _rollback genuinely runs;
  also assert the tree is restored clean.
- clusev-release.sh: |\| true on the version extraction (defensive under pipefail).
- install.sh: make the release-unit install best-effort (if/then/warn) like the WG
  block so a systemctl failure cannot abort the installer.
- .gitignore: ignore runtime run/ contents, keep the dir via run/.gitkeep.
- docker/release/README.md: dev-watcher runbook (the manual end-to-end gate).
- ReleaseBridgeTest: assert a rejected target writes no request file.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 00:16:22 +02:00
boban baecb0c6d5 fix(release): add missing confirm-modal + staged-banner lang keys
The shipped Release UI references release.confirm_title, confirm_body,
confirm_action and staged_label, but neither lang/de/release.php nor
lang/en/release.php defined them, so the R5 confirm modal and the staged
success banner leaked raw keys (R9/R17). Add the four keys to both locales.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 00:01:06 +02:00
boban c696950f25 feat(release): localized strings + audit labels for staging releases
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 23:56:09 +02:00
boban 76c1d46d12 fix(release): gate the /release route on the flag (spec gating triple)
Register the /release route only when config('clusev.release_controls') is
true — the first of the spec's three gating layers (route + component mount +
sidebar). The route file is re-included per request when uncached, so the flag
is honoured; mount() abort_unless(...) still covers the cached-route case.

The gating test re-evaluates routes/web.php after toggling the flag (the file
is read once at boot, so a runtime config()->set() alone cannot register the
route), mirroring a fresh request.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 23:48:17 +02:00
boban 4b68b8dc04 feat(release): flag-gated /release page + Deploy-to-Staging via host bridge
Adds the dev-only Release page: a flag-gated route (config('clusev.release_controls'),
enforced by the component mount() guard) and a flag-gated sidebar item, plus the
Release Livewire component. Deploy-to-Staging is irreversible, so it goes through the
shared R5 wire-elements/modal confirm — confirmDeployStaging opens the modal and
applyDeployStaging consumes the sealed ConfirmToken and runs the deployStaging executor
(the host bridge writes the request, betaN is computed host-side). Registers the
releaseStaged confirm action in the ConfirmToken allow-list.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 23:39:47 +02:00
boban 58a94d1e9a feat(release): systemd watcher units + dev run-mount + flag-gated install
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 23:28:27 +02:00
boban d4f973044b feat(release): host bridge script — cut+push a staging beta from a request
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 23:20:00 +02:00
boban 54b41591b9 feat(release): ReleaseBridge — write staging request, read host result
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 23:14:34 +02:00
boban 05c98a7b9e feat(release): ReleasePlanner — proposed beta targets from the current version
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 23:07:41 +02:00
boban 39ccf0d539 docs(plan): Phase B1 — Deploy to Staging implementation plan
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 23:03:56 +02:00
boban 366d237cb8 docs(spec): Phase B1 — Deploy to Staging (dashboard release control)
First sub-project of Phase B: a dev-only Release page with a Deploy-to-Staging
button that cuts a beta (bump config version, commit, tag vX.Y.Z-betaN, push to
Gitea) via a host release bridge — same isolation as the WireGuard bridge, so the
container never holds git/token credentials. Gated behind CLUSEV_RELEASE_CONTROLS.
B2 (public promotion via GitHub API + vault token) is a separate later spec.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 22:52:34 +02:00
boban 628c9fdbb0 fix(release): guard on internal-doc presence only; genericise .env.example IP
The earlier leak guard grepped the published tree for private URL strings, but
promote.sh / promote.test.sh contain those very strings (patterns + fixture), so
it tripped on its own source and would also ship them. Switch the guard to
internal-doc presence (CLAUDE.md/rules.md/handoff.md/kickoff-prompt.md/docs) — a
reliable pre-fix-tree signal that embeds no private identifier. Genericise the
dev VM IP in .env.example (APP_URL/REVERB_HOST) to localhost.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 22:19:19 +02:00
boban 51b8572d98 fix(release): leak guard + export-ignore + env default (review fixes)
The actual file changes for the leak-safety hardening described in a94efb0
(that commit only captured the art/ asset move):
- promote.sh: leak guard refusing to publish a tree carrying a private identifier
  (git.bave.dev / boban/clusev / clusev-staging) or an internal doc; negative test.
- .gitattributes: export-ignore kickoff-prompt.md (leaked the VM IP).
- README: reference the moved art/ SVGs (docs/ is export-ignored).
- .env.example: comment out CLUSEV_REPOSITORY so the public-repo config default
  applies (a present-but-empty value would disable the update check).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 22:16:23 +02:00
boban 437221788d fix(versions): discriminate release caches by repository + assert the UA value
- ReleaseChecker: mix a short hash of clusev.repository into the tag + changelog
  cache keys so a cached value can never be reused across a host/repo change (the
  promotion pipeline re-points CLUSEV_REPOSITORY between the private mirror and the
  public repo).
- Strengthen the GitHub User-Agent test to assert the value (Clusev-Panel), not
  mere presence — Guzzle always sets some UA, so the old check was a tautology.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 22:15:36 +02:00
boban a94efb0b0d fix(release): leak-safety hardening from adversarial review
- promote.sh: add a leak guard that refuses to publish a tree still carrying a
  private identifier (git.bave.dev / boban/clusev / clusev-staging) or an internal
  doc (CLAUDE.md, rules.md, handoff.md, kickoff-prompt.md, docs/). This makes
  promotion safe even for a tag cut BEFORE the export-ignore fixes — such a tree is
  refused instead of leaking the dev/staging repos. Negative test added.
- .gitattributes: export-ignore kickoff-prompt.md (internal bootstrap doc; leaked
  the VM IP + infra topology).
- README: move the banner/architecture SVGs out of the export-ignored docs/ into
  art/ so the public README images are actually published.
- .env.example: comment out CLUSEV_REPOSITORY — a present-but-empty value defeats
  the config default (env() returns ''), which would silently disable the public
  update check. Left unset, the public-repo default applies.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 22:15:36 +02:00
boban b00e7a49fd docs: move maintainer release-pipeline guide out of the public README
The release-pipeline + one-time-wiring section named the private staging runner
label and described internal setup — not public-facing. Move it to
docs/release-pipeline.md (export-ignore, never published) so the public README
stays clean and no private repo name leaks via the published tree.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 21:43:38 +02:00
boban ccabc1cfa6 chore(release): point public-facing URLs at github.com/clusev/clusev — 0.9.58
Default update source, README (badge/clone/home), the console + source-view
branding, and the CHANGELOG compare links now reference the public repo. Private
URLs remain only in the gitignored .env (install.sh derives CLUSEV_REPOSITORY
from the clone origin). Bump 0.9.58.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 21:41:52 +02:00
boban a92eb8182b chore(release): keep internal docs out of the public repo
promote.sh copies a release tree with git archive, which honours export-ignore.
Mark CLAUDE.md, rules.md, handoff.md and docs/ as export-ignore so internal dev
notes (and the Gitea URL they contain) never reach the public repo; drop the
stock CHANGELOG.md export-ignore so the public Versions page can fetch it. Extend
promote.test.sh to assert internal files are excluded and CHANGELOG.md is kept.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 21:41:52 +02:00
boban 586fac7132 feat(versions): support GitHub host in the update check
ReleaseChecker + the changelog fetch were hardwired to the Gitea API path, so a
GitHub-hosted public repo would never resolve a release. Centralise the repo-URL
parsing and branch by host: GitHub uses api.github.com for tags and
raw.githubusercontent.com for CHANGELOG.md (with the User-Agent GitHub requires);
Gitea keeps its /api/v1 paths. The changelog fetch moves into ReleaseChecker so
all host logic lives in one place.

Make the update-check tests hermetic: they now set clusev.repository themselves
(neutral host) instead of depending on the ambient .env — so they pass in CI with
an empty .env.example — and add GitHub-host coverage for tags + changelog.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 21:41:52 +02:00
boban 44313d5ac5 chore: release 0.9.57 — release-promotion phase A (CI/CD scaffolding)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 21:20:07 +02:00
boban 8e9499780e fix(release): review polish — promote idempotency, CI hardening, docs
- promote.sh: fail fast before mutating the public tree if the tag was already
  promoted (avoids a half-commit left on the runner on re-dispatch).
- ci-staging.yml: pass STAGING_PROJECT_DIR via env + :? guard instead of a
  ${{ }} expression in run: (clears the template-injection lint, fails loudly
  on misconfig).
- README: PUBLIC_REPO_URL was never read by any workflow — keep only the slug.
- set-repository-url.test.sh: chmod +x for mode parity with promote.test.sh.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 21:18:44 +02:00
boban 4aa55b7124 ci: workflow_dispatch promotion to public (beta) + beta-to-stable
Promotion workflows for the release pipeline. promote-public publishes a tag to
the public repo's beta channel; promote-stable re-tags a finalised beta as a
stable vX.Y.Z. Both bound to the 'production' environment (required reviewer),
reading PUBLIC_REPO_SLUG/PUBLIC_REPO_TOKEN. Tree copy via scripts/promote.sh
excludes .git/.github so private CI never reaches the public repo.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 21:18:32 +02:00
boban e2dc9b17a4 docs: release pipeline (3-repo promotion, channels, wiring)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 21:10:03 +02:00
boban c4f90b0830 ci: test on v* tags, optional self-hosted staging deploy on betas
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 21:03:14 +02:00
boban 79bd08b0d8 fixup(release): promote.sh --allow-empty so identical-tree stable promotion still tags
Stable re-tags the same source commit as the last beta, so the extracted tree
is byte-identical and `git commit` would abort on "nothing to commit" under
set -euo pipefail, leaving the stable tag uncreated. Use --allow-empty and add
a promote.test.sh case that promotes a second identical-tree tag into the same
pub repo to guard the regression.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 20:59:24 +02:00
boban 0dd7564ab0 feat(release): promote.sh — clean per-release tree copy to the public repo
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 20:53:44 +02:00
boban c6e759a3b4 fixup(install): align set-repository-url write-back with plan if/else structure
Restore the plan's if-grep / replace / else-append skeleton in the
CLUSEV_REPOSITORY write-back. The replace branch deletes existing
line(s) then appends once so duplicates collapse to a single entry,
keeping scripts/set-repository-url.test.sh green (the plan's verbatim
in-place sed leaves two identical lines and fails the duplicate-line
assertion).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 20:46:59 +02:00
boban ee111bfa9a feat(install): derive CLUSEV_REPOSITORY from the clone origin
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 20:43:25 +02:00
boban a1386b0533 refactor(config): repository URL is env-driven (no private URL in tracked files)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 20:35:59 +02:00
boban 06b5600641 docs(plan): release-promotion phase A implementation plan
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 20:33:03 +02:00
boban 30034a564b docs(spec): full release model — channels, dashboard buttons, two phases
Capture the maintainer's release workflow: dev-dashboard buttons drive the
pipeline (Deploy to Staging / Deploy to Public / Promote to Stable). Beta =
vX.Y.Z-betaN on staging, Stable = vX.Y.Z on public; public serves both channels.
Public promotion triggered via the GitHub API with a deploy token in the
encrypted vault. Decomposed: Phase A = git/CI/CD + config-URL infra (buildable
now, URL-agnostic); Phase B = the dashboard release-control UI (own spec, after
A + the repos exist).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 19:34:21 +02:00
boban 70da2718fb docs(spec): clarify URL handling — public default in config, private URLs only in .env
Per the maintainer: dev/staging may hold any URL (in their gitignored .env);
the public repo's tracked files carry only the public URL. So config/clusev.php
defaults to the public URL, private (Gitea/GitHub-private) URLs live solely in
gitignored .env, and install.sh derives CLUSEV_REPOSITORY from the clone origin.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 11:22:34 +02:00
boban e2b4a968c9 docs(spec): 3-repo release promotion (Gitea → GitHub private → public)
Design for promoting Clusev dev → staging → public across three repos with
GitHub Actions CI, a manual gate before public, and a clean public history.
Core: no repository URL in tracked files — derived from the clone origin, kept
in gitignored .env per environment, so the public repo never exposes the
private/Gitea URLs and the URLs plug in later when the GitHub repos exist.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 07:03:24 +02:00