Release smoke¶
Hosted CI is necessary but not sufficient for a Spectre release. Several high-value surfaces are shipped on all three desktop OSes while full interactive e2e is Linux/macOS-only (or unit-only) on GitHub-hosted runners. A scoped pre-tag smoke on real desktops closes that gap before anyone tags.
This page is part of the release process. Do not tag a release without a written smoke plan for that version and a completed results table.
Related: Publishing, Capability matrix, Stability policy, Testing policy.
When it runs¶
| Phase | What |
|---|---|
| Before tag | Author the scoped plan (below), run hard cells, paste results into the release PR / issue / chat. Hard red → fix or drop the claim before tagging. |
| Tag / CI | release.yml (runtime matrix, helpers, notarization, verifyMavenLocalPublication, Central upload, draft GH release). |
| After promote | Central checker + undraft + package channels (see Publishing). Optional quick re-smoke of CLI from the public release archive. |
Smoke does not replace green main CI or a green release workflow. It
covers paths CI cannot or does not execute fail-closed.
How to scope a release (every time)¶
Produce a short plan for this version before running anything. Keep it in
the release notes draft, a PR description, or .plans/<version>-smoke.md
(gitignored). Minimum contents:
- Version and base tag (e.g.
v0.3.0→v0.4.0). - Delta inventory — features and packaging changes since the last tag: Map each user-visible theme to at least one smoke cell (or mark “CI-only / unit-covered”).
- Baseline regression set (always include — see Baseline hard cells).
- Delta hard cells — new or changed paths that lack fail-closed multi-OS
evidence (especially anything gated
@EnabledOnOs(LINUX, MAC)while docs claim Windows). - Soft cells — optional; failure does not block the tag if the capability matrix already says Experimental / Not yet CI-executed.
- Results table — OS × cell, pass/fail/N/A + one-line note.
Signals that something must be a hard cell¶
Add a hard physical/desktop cell when any of these hold:
- Hosted tests use
@EnabledOnOs(OS.LINUX, OS.MAC)(or skip Windows) for a surface docs claim on Windows. CapabilityMatrixmarks the cell Not yet CI-executed or Experimental while release notes would still market it as “works.”- New native helper packaging, notarization shape, or CLI distribution layout.
- New default-on test behaviour (e.g. failure artifacts) that consumers hit without opt-in.
- New attach/bootstrap path (inject, launch-and-attach, protocol version bump).
- Only unit/mocked tests exist for a CLI/daemon feature that users run live.
Use testing/.../contract/CapabilityMatrix.kt and
docs/guide/capability-matrix.md as the machine-checkable inventory of claimed
vs unproven cells.
Permanent CI gaps (always consider for hard smoke)¶
These are structural, not one-release accidents:
| Surface | Hosted CI | Physical smoke |
|---|---|---|
| Agent attach + contract corpus (live UI) | Linux Xvfb + macOS desktop; Windows transport/ACL unit tests | Windows headed desktop (not SSH-only for capture) |
| Agent Windows UI e2e | Opt-in only: -Pspectre.agent.attachE2e.allowWindows=true |
Run that property on Mattone-class boxes |
| Agent inject attach | Linux + macOS e2e | Windows inject fixture (no preinstalled core) |
| Launch-and-attach e2e | Linux + macOS | Windows direct java (and Gradle if claimed) |
| CLI daemon + live fixture | Linux + macOS | Windows release-shaped CLI binary |
| Daemon-owned window recording | macOS e2e when TCC allows; heavy unit mocks elsewhere | macOS live record; Windows WGC only from an interactive console (SSH sessions fail WGC / black pixels) |
macOS SpectreCaptureHelper.app + TCC row |
Helper build on PR; notary on tag | Extract from jar; permissions check; one SCK capture; notarize remains CI |
| Windows multi-file WGC helper | Packaging contract in Gradle + portal checker | Extract from jar; live capture from interactive console |
| Stock IntelliJ inject (no spectre-core in IDE) | Recipe + instrumented ide-uitest only | Manual recipe when that path is in release notes |
Popup compose.layers.type=WINDOW on Windows |
Skipped (upstream skiko) | Do not claim; optional recheck after runtime bumps |
Windows session note: A box can be “interactive” for console login yet still fail Screen
Capture / WGC under SSH (e.g. 0x80070424, black Skiko pixels). Treat attach (semantics)
as SSH-safe when proven; treat pixel capture / WGC as requiring a local interactive
desktop session (RDP console or physical).
Environments¶
| Host | Role |
|---|---|
| macOS (dev machine) | Primary: library, agent, CLI, SCK helper, permissions |
Windows (logged-in desktop; optional ssh for non-WGC) |
Prefer one-liner Windows smoke script on the console user session |
| Linux (Hyper-V VM from Windows host, or native) | Agent attach under Xvfb or real display; package smoke; Wayland only if session is real |
Headless windows-latest is not a substitute for B9–B13.
Prefer release-shaped artifacts:
./gradlew check
./gradlew publishToMavenLocal -PVERSION_NAME=<version>-rc.smoke
# helpers: real platform builds where possible; stub only what the host cannot build
./gradlew verifyMavenLocalPublication # flags per docs/PUBLISHING.md
CLI: package the host OS binary (:cli:package*) or use the draft release archive
after tag. Prefer the same helper layout consumers extract from Maven jars.
Baseline hard cells¶
Run these for every release unless a cell is explicitly N/A with reason (e.g. no Linux display for this cut and no Linux claim change).
| ID | OS | Cell | Pass criteria |
|---|---|---|---|
| B1 | macOS | ./gradlew check |
Green on release SHA |
| B2 | macOS | Maven Local core + testing consume | Compile/run a one-liner or sample against -PVERSION_NAME=… |
| B3 | macOS | Failure artifacts | Failing Spectre JUnit test writes build/reports/spectre/** (capture.json + PNG) |
| B4 | macOS | Atomic capture | In-process or CLI; schemaVersion matches shipped skill/docs |
| B5 | macOS | Agent attach (preinstalled core) | Non-empty windows + known tag; clean detach |
| B6 | macOS | Agent inject | Compose-only target; inject bootstrap; non-empty tree; detach |
| B7 | macOS | CLI daemon fixture | spectre attach (or package binary) → tree/capture against live fixture |
| B8 | macOS | Capture helper + TCC | Helper is app-bundle identity; spectre permissions check; one screenshot/record |
| B9 | Windows | Agent attach (core) | Same as B5; prefer opt-in e2e (recipe below). SSH OK if semantics-only. |
| B10 | Windows | Agent inject | Same as B6 — does not ship “three OS agent” without this |
| B11 | Windows | Launch-and-attach | Direct java launch + attach (Gradle optional) |
| B12 | Windows | CLI package + attach | Packaged spectre (or packageWindowsX64) attaches to fixture |
| B13 | Windows | WGC helper | Multi-file extract (SSH OK) and live capture from interactive console (not SSH) |
| B14 | Linux | Agent attach | Xvfb or real display; non-empty tree |
| B15 | any | Publication shape | verifyMavenLocalPublication (and portal checker once deployment exists) |
Soft (every release if time; never block solely on focus flakes):
| ID | Cell |
|---|---|
| S1 | Agent typeText / pressKey (known OS-focus flakes; matrix Experimental where noted) |
| S2 | Hot Reload settle e2e (needs HR-enabled target) |
| S3 | Stock IntelliJ inject recipe (manual) |
| S4 | JUnit 4 failure-artifact path (JUnit 5 is the hard path) |
| S5 | Full kill-target mid-record finalize |
Results table (required)¶
Copy into the release record:
Version: vX.Y.Z Base: vA.B.C SHA: <full>
Operator: <name> Date: <ISO>
| ID | OS | Cell | Result | Note |
|-----|---------|------------------------------|--------|------|
| B1 | macOS | check | | |
| B5 | macOS | attach (core) | | |
| B6 | macOS | inject | | |
| B9 | Windows | attach (core) | | |
| B10 | Windows | inject | | |
| … | | | | |
| D1 | … | <delta cell> | | |
Hard failures: none | <list>
Soft notes: …
Result values: pass | fail | n/a (with reason).
Empty hard cells or n/a for “no display” on a claimed platform block the tag.
Windows one-liner script¶
When you have a Windows desktop for a few minutes, run one command from the repo root (interactive logon session preferred):
Or from anywhere:
What it does (no second terminal):
- Opt-in agent UI e2e: attach + inject + launch-and-attach
(-Pspectre.agent.attachE2e.allowWindows=true, properly quoted for PowerShell) - WGC region smoke (
:recording:runWindowsGraphicsCaptureRegionSmoke) :cli:packageWindowsX64then packagedspectre launch --once --app-name ComposeFixtureMain -- gradlew :agent-test-fixture:run
Writes build/smoke/windows-release-smoke.json and exits non-zero on any failed step.
Flags:
| Flag | Effect |
|---|---|
-SkipAgentE2e |
Skip Gradle attach/inject/launch tests |
-SkipWgc |
Skip region recording smoke |
-SkipCli |
Skip package + spectre launch |
-SkipPackageCli |
Reuse existing spectre.exe (still runs launch) |
This is manual, operator-driven automation — not hosted CI. Use it so release smoke is not multi-terminal faff.
Recipes (common hard cells)¶
Agent attach on Windows (product + opt-in e2e)¶
Product path (all OSes including Windows 10 1803+):
Optional full UI e2e on a physical Windows desktop (not hosted CI default):
# bash / zsh / cmd
./gradlew :agent:test \
-Pspectre.agent.attachE2e.allowWindows=true \
--tests '*AgentAttachIntegration*'
# PowerShell: quote the -P argument (otherwise PS splits on dots after -Pspectre)
./gradlew :agent:test `
"-Pspectre.agent.attachE2e.allowWindows=true" `
--tests '*AgentAttachIntegration*'
See Agent attach. Do not enable this property on headless
windows-latest as a fail-closed gate without a headed runner story.
Agent inject (Linux / macOS / Windows)¶
Same intent as AgentInjectAttachIntegrationTest:
- Build
spectre-agent-runtimewith nestedMETA-INF/spectre/inject-runtime.jar. - Start the inject fixture without
spectre-coreon the child classpath (InjectComposeFixtureMain/ classpath strip helpers in:agenttests). - Target JVM:
-XX:+EnableDynamicAgentLoading, non-headless where UI is required. - Attacher:
AgentAttach.attach(pid, AttachOptions(agentJarPath = …)). - Assert fixture window, non-empty nodes / test tag, clean detach.
- Prefer stderr line that core was injected (not found preinstalled).
On Windows, attach, inject, and launch UI e2e share
-Pspectre.agent.attachE2e.allowWindows=true (or the one-liner script).
Launch-and-attach¶
LaunchAndAttach / spectre launch with a short-lived Compose fixture or
java -jar. Assert readiness stages complete and attach returns a usable
automator. Separate failures: process death vs attach vs empty tree. Covered by the
Windows smoke script’s agent e2e + packaged spectre launch --once step.
macOS Capture Helper¶
- Consume
spectre-recording-macosfrom Maven Local or the release jar. - Confirm tree is
native/macos/SpectreCaptureHelper.app/..., not a bare Mach-O. spectre permissions check/requestas needed; Settings row should name Spectre Capture Helper.- One still or short window/region capture via library or CLI.
Windows WGC helper¶
- Open
spectre-recording-windowsjar; confirm dual-arch multi-file layout (see packaging contract inbuildSrc/ Publishing). Portal checker and Gradle both assert multi-file basenames. - Packaging may be verified over SSH (extract + list files).
- Live capture: prefer the Windows smoke script or
:recording:runWindowsGraphicsCaptureRegionSmokeon a logged-in desktop. SSH can work but sometimes yields WGC0x80070424or black frames.
After a successful smoke¶
- Tag only the smoked SHA (
vX.Y.Z). - Watch
release.yml(matrix, notary, publish). - Promote Central + undraft per Publishing.
- Optionally re-run B7/B12 against public CLI archives once package channels land.
- If smoke discovered a permanent evidence gap, update
CapabilityMatrix/ the guide rather than leaving docs overselling CI.
What smoke is not¶
- Not a full substitute for runtime-matrix or validation workflows.
- Not permission to enable flaky Windows e2e on headless GH runners.
- Not required for every PR — only for release tags (and RC tags if you cut them).
- Not a place to re-litigate Experimental APIs; soft-smoke them or keep them out of headline release claims.