Skip to content

Commit 552a90c

Browse files
authored
Merge pull request #72 from TrentBrown/tb-portreeve-release-versioning
Fix coordinated preview release identity
2 parents e0fc14d + c0d0e8c commit 552a90c

37 files changed

Lines changed: 1228 additions & 91 deletions

.agents/skills/release-portreeve/SKILL.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ bun run release:prepare -- \
5555
--version 0.1.0-preview.1
5656
```
5757

58+
The requested version is the installed identity of every release output. Preview builds
59+
inject the full prerelease into the CLI, client package, Desktop metadata, formula, and
60+
cask while leaving checked-in package files at the matching semantic core. Never edit
61+
multiple package files as an operator precondition or accept a formula/cask that drops
62+
the prerelease suffix.
63+
5864
For the complete hosted rehearsal:
5965

6066
```sh

apps/desktop/main/index.js

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ import { createStackDocumentService } from './stack-document.js';
2828
import { registerDesktopIpc } from './ipc.js';
2929
import { registerRendererProtocol } from './protocol.js';
3030
import { createUpdateAdapter } from './update.js';
31-
import { resolveDesktopReleaseChannel } from './release-channel.js';
31+
import {
32+
resolveDesktopReleaseChannel,
33+
resolveDesktopReleaseVersion,
34+
} from './release-channel.js';
3235
import { desktopUpdateStatePath, resolveDesktopUserDataPath } from './user-data.js';
3336
import { createLauncherRuntime } from '../../../src/launcher/runtime.js';
3437
import { resolveRuntimePaths } from '../../../src/platform/paths.js';
@@ -104,8 +107,13 @@ async function startDesktop() {
104107
? { overridePath: process.env.PORTREEVE_DESKTOP_CLI_PATH }
105108
: {}),
106109
});
107-
const releaseChannel = resolveDesktopReleaseChannel(
108-
JSON.parse(await readFile(resolve(app.getAppPath(), 'package.json'), 'utf8')),
110+
const desktopMetadata = JSON.parse(
111+
await readFile(resolve(app.getAppPath(), 'package.json'), 'utf8'),
112+
);
113+
const releaseChannel = resolveDesktopReleaseChannel(desktopMetadata);
114+
const desktopVersion = resolveDesktopReleaseVersion(
115+
desktopMetadata,
116+
app.getVersion(),
109117
);
110118
diagnose('artifact-verified', artifact.filename);
111119
const lifecycle = createDesktopLifecycleController(artifact);
@@ -114,7 +122,7 @@ async function startDesktop() {
114122
console.log(
115123
`PORTREEVE_DESKTOP_SMOKE ${JSON.stringify({
116124
schemaVersion: 1,
117-
desktopVersion: app.getVersion(),
125+
desktopVersion,
118126
controllerVersion: lifecycle.compatibility.version,
119127
artifactVersion: artifact.version,
120128
mode: status.mode,
@@ -161,7 +169,7 @@ async function startDesktop() {
161169
const coordinator = createStateCoordinator({
162170
artifact: {
163171
...artifact,
164-
desktopVersion: app.getVersion(),
172+
desktopVersion,
165173
controller: lifecycle.compatibility,
166174
},
167175
lifecycle,
@@ -180,7 +188,7 @@ async function startDesktop() {
180188
}),
181189
launchers,
182190
updates: createUpdateAdapter({
183-
desktopVersion: app.getVersion(),
191+
desktopVersion,
184192
channel: releaseChannel,
185193
statePath: desktopUpdateStatePath(app.getPath('userData')),
186194
openExternal: (url) => shell.openExternal(url),

apps/desktop/main/release-channel.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,19 @@ export function resolveDesktopReleaseChannel(metadata) {
1212
}
1313
return /** @type {const} */ ('preview');
1414
}
15+
16+
/** @param {unknown} metadata @param {string} fallback */
17+
export function resolveDesktopReleaseVersion(metadata, fallback) {
18+
if (
19+
metadata !== null &&
20+
typeof metadata === 'object' &&
21+
'portreeveReleaseVersion' in metadata &&
22+
typeof metadata.portreeveReleaseVersion === 'string' &&
23+
/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/u.test(
24+
metadata.portreeveReleaseVersion,
25+
)
26+
) {
27+
return metadata.portreeveReleaseVersion;
28+
}
29+
return fallback;
30+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Completion Report - Coordinated Preview Release Identity
2+
3+
**Status:** Complete and awaiting human review in PR
4+
[#72](https://github.com/TrentBrown/portreeve/pull/72)
5+
6+
## Outcome
7+
8+
PortReeve now treats an operator-supplied release version such as
9+
`0.1.0-preview.4` as the observable identity of every distributed component. CLI
10+
binaries, the client archive, records and manifests, Homebrew metadata, Desktop
11+
metadata and update comparison, and release filenames agree on that identity.
12+
13+
Checked-in packages remain at their base development version. The release engine
14+
requires the requested version to share that semantic core and injects the full version
15+
only into immutable outputs. Apple bundle metadata remains numeric while PortReeve's
16+
own packaged metadata retains the prerelease.
17+
18+
## Acceptance and Rubric
19+
20+
All acceptance criteria AC1-AC5 and rubric criteria R1-R5 pass. The complete evidence
21+
packet is [`pr-72/boundary.json`](pr-72/boundary.json).
22+
23+
## Verification Summary
24+
25+
- Full repository gate: 550 tests passed, 0 failed; typecheck, lint, formatting, and
26+
generated documentation passed.
27+
- Real preview.4 candidate: all four native formats built; the macOS ARM64 executable
28+
reported preview.4 and passed native server smoke.
29+
- Desktop: ARM64 package and launch smoke reported preview.4 while Apple's short version
30+
remained 0.1.0.
31+
- Homebrew: the generated formula and cask retain preview.4, and Homebrew orders it after
32+
preview.3 and before stable 0.1.0.
33+
- Publication: not performed; preview.3 and public distribution state remain untouched.
34+
35+
## Retention
36+
37+
The feature-final workflow check reports `tracked`: every feature-record file is retained
38+
in Git, and no human retention exception is required.
39+
40+
## Remaining Human Action
41+
42+
Review and merge PR #72. A later, separately authorized preview release will provide the
43+
first public end-to-end exercise of the corrected identity.
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Decisions - tb-portreeve-release-versioning
2+
3+
**Feature start:** 2026-08-18
4+
5+
Permanent record of decisions promoted from `scratchpad.md`.
6+
7+
---
8+
9+
## Inject release identity without mutating source package versions
10+
11+
**Confidence:** HIGH
12+
13+
**Blast Radius:** native builds, client packing, Desktop packaging, records,
14+
Homebrew metadata, and update checks
15+
16+
Keep checked-in package versions as the development base and inject the
17+
operator-approved coordinated version into immutable release outputs. Validate
18+
that their semantic cores match before building.
19+
20+
**Triggered by:** preview.2 and preview.3 both appearing to Homebrew as `0.1.0`
21+
22+
**Alternatives considered:**
23+
- Homebrew revision only - leaves every other distributed identity inconsistent.
24+
- Edit all package versions before release - adds manual ceremony and conflicts
25+
with the clean-source release precondition.
26+
27+
**Promoted:** 2026-08-18. PR: #72.
28+
29+
---
30+
31+
## Preserve numeric Apple bundle version separately
32+
33+
**Confidence:** HIGH
34+
35+
**Blast Radius:** macOS packaging and Desktop update identity
36+
37+
Keep the source base version in `CFBundleShortVersionString`, but embed the
38+
coordinated semantic release version in PortReeve package metadata and use it
39+
for product reporting and update comparisons.
40+
41+
**Triggered by:** Apple bundle version constraints do not model semantic
42+
prerelease identifiers cleanly.
43+
44+
**Alternatives considered:**
45+
- Put the prerelease string directly in the Apple bundle version - avoid because
46+
stable signing/notarization will require Apple-conformant numeric metadata.
47+
48+
**Promoted:** 2026-08-18. PR: #72.
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Design - tb-portreeve-release-versioning
2+
3+
**Feature:** `tb-portreeve-release-versioning`
4+
**Created:** 2026-08-18
5+
**Status:** approved
6+
7+
## Problem
8+
9+
PortReeve currently records `0.1.0-preview.N` as the GitHub release identity but
10+
builds and publishes components identified as `0.1.0`. Homebrew compares the
11+
component version, so it cannot discover a later preview of the same base
12+
version. Desktop update comparison has the same blind spot.
13+
14+
## Chosen model
15+
16+
The operator-supplied coordinated semantic release version is authoritative for
17+
every distributed PortReeve surface:
18+
19+
- compiled CLI behavior and native artifact filenames;
20+
- packed JavaScript client metadata and runtime client identity;
21+
- release record component versions and manifest versions;
22+
- Homebrew formula and cask versions;
23+
- Desktop DMG filenames, packaged PortReeve release identity, update metadata,
24+
and update comparison.
25+
26+
Checked-in package files retain a base development version. Release building
27+
validates that the coordinated version's semantic core equals that base version,
28+
then injects the coordinated version into immutable release outputs. This keeps
29+
release invocation deterministic and avoids a manual multi-file version-bump
30+
precondition.
31+
32+
The macOS `CFBundleShortVersionString` may remain the numeric base version because
33+
Apple defines that field independently from semantic prerelease identity. A
34+
packaged PortReeve metadata field carries the coordinated version, and Desktop
35+
uses it instead of `app.getVersion()` for product identity and update checks.
36+
37+
## Invariants
38+
39+
1. All release-record component versions equal the coordinated release version.
40+
2. Every native executable reports the coordinated version through `--version`.
41+
3. Formula and cask versions equal the coordinated version.
42+
4. The packed client package and exported client version equal the coordinated
43+
version.
44+
5. The packaged Desktop reports and compares the coordinated version even when
45+
its Apple bundle version remains numeric.
46+
6. A stable release has no prerelease suffix; a preview release has one; both
47+
must share their semantic core with source component versions.
48+
7. Existing public releases and assets are never rewritten.
49+
50+
## Rejected alternatives
51+
52+
- **Homebrew-only revision bumps:** fixes one installer while leaving CLI,
53+
Desktop updates, records, and client identity inconsistent.
54+
- **Manual edits to all package files before release:** introduces an operator
55+
ceremony and dirty-checkout problems into an otherwise deterministic pipeline.
56+
- **Treat preview.3 as a one-time exception:** repeats the defect at preview.4.
57+
58+
## Approval record
59+
60+
Approved by the user on 2026-08-18 through the instruction to correct the
61+
recurring preview-version problem immediately.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Interview - tb-portreeve-release-versioning
2+
3+
**Feature start:** 2026-08-18
4+
**Status:** complete
5+
6+
## Trigger
7+
8+
Publishing `0.1.0-preview.3` exposed that the public release identity and every
9+
installed component still reported `0.1.0`. Homebrew therefore treated an
10+
installed preview.2 as current after the tap moved to preview.3.
11+
12+
## Settled intent
13+
14+
- The defect is recurring for every preview sharing the same base version, not
15+
a one-time repair for the current sole user.
16+
- The coordinated semantic release version supplied to release preparation is
17+
the installed identity of the CLI, JavaScript client, Desktop release, formula,
18+
and cask.
19+
- Homebrew must order successive previews and the eventual stable version as
20+
`preview.2 < preview.3 < preview.4 < 0.1.0`.
21+
- Development checkouts may retain the base source version (`0.1.0`). Release
22+
preparation must inject the coordinated identity deterministically rather
23+
than requiring an operator to edit several package files before every run.
24+
- macOS bundle metadata that is constrained to numeric versions may retain the
25+
base version. PortReeve's own packaged runtime identity and update comparison
26+
must still use the coordinated prerelease version.
27+
- Published preview.3 remains immutable. The correction applies to future
28+
releases beginning with preview.4.
29+
30+
## Boundaries
31+
32+
- Do not republish or alter preview.3.
33+
- Do not add npm publication; the packed client remains release evidence only.
34+
- Preserve the build-once evidence and protected publication model.
35+
- Reject a coordinated release whose semantic core does not match the checked-in
36+
base component version.
37+
38+
## Approval
39+
40+
The user approved correcting this immediately after the release audit explained
41+
the recurring behavior and recommended one coordinated version across distributed
42+
surfaces.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Issues - tb-portreeve-release-versioning
2+
3+
**Feature:** `tb-portreeve-release-versioning`
4+
**Spec:** [`spec.md`](spec.md)
5+
**Plan:** [`plan.md`](plan.md)
6+
**Created:** 2026-08-18
7+
8+
Operational task breakdown derived from the plan.
9+
10+
## I-1 - Establish coordinated version mechanics
11+
12+
- **Status:** in-review
13+
- **Estimate:** 2h
14+
- **Plan steps:** P1, P2
15+
- **Rubric criteria:** R1, R2, R4
16+
- **Depends on:** none
17+
- **PR:** [#72](https://github.com/TrentBrown/portreeve/pull/72)
18+
19+
Validate release/source compatibility, inject the release identity into compiled
20+
CLI artifacts, and stage the client archive with the same identity.
21+
22+
## I-2 - Coordinate Desktop and installer identity
23+
24+
- **Status:** in-review
25+
- **Estimate:** 2h
26+
- **Plan steps:** P3
27+
- **Rubric criteria:** R2, R3
28+
- **Depends on:** I-1
29+
- **PR:** [#72](https://github.com/TrentBrown/portreeve/pull/72)
30+
31+
Use coordinated release identity for Desktop runtime/update surfaces, DMGs, and
32+
the Homebrew cask while preserving numeric Apple bundle metadata.
33+
34+
## I-3 - Verify and document release versioning
35+
36+
- **Status:** in-review
37+
- **Estimate:** 2h
38+
- **Plan steps:** P4
39+
- **Rubric criteria:** R1-R5
40+
- **Depends on:** I-1, I-2
41+
- **PR:** [#72](https://github.com/TrentBrown/portreeve/pull/72)
42+
43+
Update the runbook and exercise focused, runtime, Homebrew, and full checks.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Plan - tb-portreeve-release-versioning
2+
3+
**Feature:** `tb-portreeve-release-versioning`
4+
**Spec:** [`spec.md`](spec.md)
5+
**Design:** [`design.md`](design.md)
6+
**Interview:** [`interview.md`](interview.md)
7+
**Created:** 2026-08-18
8+
9+
**Implementation status:** Complete in PR [#72](https://github.com/TrentBrown/portreeve/pull/72); awaiting review.
10+
11+
## Inputs
12+
13+
- `spec.md` controls scope, required behavior, and rubric mapping.
14+
- `design.md` supplies the chosen architecture, constraints, and boundaries.
15+
- `interview.md` supplies supporting examples, rationale, references, and edge cases.
16+
17+
## Strategy
18+
19+
Introduce release-version helpers and deterministic build-time injection while
20+
leaving checked-in package versions as development bases. Flow the coordinated
21+
version through native build, staged client packing, Desktop packaging, Homebrew
22+
generation, records, and update comparison. Verify both metadata and executable
23+
behavior with preview.4 fixtures.
24+
25+
## Steps
26+
27+
- **P1.** Add semantic-core validation and a reusable Bun build-time server
28+
version override. **Advances:** R1, R4.
29+
- **P2.** Use the coordinated identity for native artifact names, compiled CLI,
30+
staged client package/version source, manifests, records, and formula.
31+
**Advances:** R1, R2, R4.
32+
- **P3.** Flow the coordinated identity through Desktop bundles, DMG/cask
33+
metadata, runtime reporting, and update checks without placing a prerelease in
34+
constrained Apple numeric version fields. **Advances:** R2, R3.
35+
- **P4.** Update runbook and focused tests; prove Homebrew ordering, candidate
36+
smoke behavior, and full repository health. **Advances:** R1-R5.
37+
38+
## Verification
39+
40+
- Focused release and Desktop unit/integration tests.
41+
- Local native preview build and `--version` execution on the host architecture.
42+
- Disposable Homebrew preview-upgrade behavior where practical.
43+
- `bun run check`.
44+
- **Final step:** Run full rubric evaluation and produce the completion report.

0 commit comments

Comments
 (0)