# SPDX-License-Identifier: MPL-2.0
# Launcher Standard Review — 2026-05-26
# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath)
#
# Machine-readable manifest of the launcher-standard review campaign run
# on 2026-05-26. Prose companion: launcher-standard-review-2026-05-26.adoc.
#
# Purpose: give tooling (PR-batching bots, change-impact analyzers,
# launch-scaffolder regenerators) a single document to enumerate the
# 8-PR set, their classes, their file footprints, and their merge
# coordination constraints, without re-parsing 8 separate PR bodies.
#
# When all 8 PRs have merged this file becomes a historical record;
# its `status = "open"` fields can be flipped to "merged" with the
# merge SHA recorded.

@abstract:
8-PR campaign reviewing and hardening the launcher standard. 3
contradictions removed, 1 blocker fixed (hard-coded eclipse-mount
literal replaced with a search ladder), 1 security issue closed
(`/tmp/` symlink-attack on PID/log), 1 forcing-function CI gate added,
2 new required behaviours specified (`--version`, NO_COLOR), 2 new
contract areas given reference implementations (soft-attach +
gui-error). All PRs independent off main, GPG-signed.
@end

[campaign]
id = "launcher-standard-review-2026-05-26"
filed-date = "2026-05-26"
scope = "launcher/ + docs/UX-standards/launcher-standard.adoc + LM-LA-LIFECYCLE-STANDARD.adoc"
pr-count = 8
status = "all-open-awaiting-owner-merge"
prose-companion = "docs/audits/launcher-standard-review-2026-05-26.adoc"

[[pr]]
number = 170
branch = "fix/launcher-standard-contradictions"
title = "fix(launcher-standard): resolve 3 cross-doc contradictions"
class = "contradictions"
files = [
    "launcher/launcher-standard.a2ml",
    "docs/UX-standards/LM-LA-LIFECYCLE-STANDARD.adoc",
]
touches-spec-version = true
spec-version-target = "0.3.0"
addresses = [
    "browser-alias-listed-as-required-mode",
    "integ-disinteg-supersedence-unmirrored",
    "sudo-vs-no-elevated-privileges-conflict",
]
status = "open"

[[pr]]
number = 171
branch = "fix/launcher-standard-path-resolution"
title = "fix(launcher-standard): replace hard-coded /var/mnt/eclipse/... with a search-ladder"
class = "blocker-path-resolution"
files = [
    "launcher/launcher-standard.a2ml",
    "docs/UX-standards/launcher-standard.adoc",
    "launcher/README.adoc",
    "launcher/resolve-desktop-tools.sh",  # new
]
touches-spec-version = false
addresses = [
    "var-mnt-eclipse-literal-broken-on-other-hosts",
    "no-bash-reference-impl-for-resolution",
]
new-files = ["launcher/resolve-desktop-tools.sh"]
status = "open"

[[pr]]
number = 172
branch = "ci/launcher-standard-lockstep"
title = "ci: add launcher-standard prose↔a2ml lock-step gate"
class = "ci-forcing-function"
files = [
    ".github/workflows/launcher-standard-lockstep.yml",  # new
]
touches-spec-version = false
addresses = [
    "prose-a2ml-drift-not-enforced",
]
new-files = [".github/workflows/launcher-standard-lockstep.yml"]
fires-on = [
    "launcher/launcher-standard.a2ml",
    "docs/UX-standards/launcher-standard.adoc",
]
status = "open"

[[pr]]
number = 173
branch = "feat/launcher-version-mode"
title = "feat(launcher-standard): require --version mode with machine-greppable format"
class = "missing-mode"
files = [
    "launcher/launcher-standard.a2ml",
    "docs/UX-standards/launcher-standard.adoc",
]
touches-spec-version = false
addresses = [
    "no-version-mode-required",
]
required-format = "{app-name} {version} ({build-sha-short}) [{platform}]"
status = "open"

[[pr]]
number = 175
branch = "fix/launcher-xdg-pid-log-paths"
title = "fix(launcher-standard): move PID/log to XDG dirs (security: symlink-attack hardening)"
class = "security-xdg-hardening"
files = [
    "launcher/launcher-standard.a2ml",
    "docs/UX-standards/launcher-standard.adoc",
]
touches-spec-version = true
spec-version-target = "0.4.0"  # if landing after #170; else 0.3.0 and #170 bumps to 0.4.0
addresses = [
    "tmp-pid-symlink-attack-vector",
    "tmp-log-symlink-attack-vector",
]
recovery-note = "PR #175 was re-pushed force-with-lease after a parallel-session amend incident contaminated the original commit with 327 lines of unrelated WIP. The final pushed commit (signed GPG) contains only the intended 2-file XDG change."
status = "open"

[[pr]]
number = 176
branch = "fix/keepopen-honour-no-color"
title = "fix(keepopen): honour NO_COLOR and auto-strip ANSI for non-TTY stdout"
class = "ux-no-color"
files = [
    "launcher/keepopen.sh",
]
touches-spec-version = false
addresses = [
    "keepopen-emits-ansi-when-piped",
    "no-color-env-not-honoured",
]
triggers-lockstep-gate = false  # not in the 2-file lock-step group
status = "open"

[[pr]]
number = 177
branch = "feat/launcher-browser-and-wait-timing"
title = "feat(launcher-standard): cross-platform browser launch + env-overridable wait timing"
class = "cross-platform-and-tunable"
files = [
    "launcher/launcher-standard.a2ml",
    "docs/UX-standards/launcher-standard.adoc",
]
touches-spec-version = false
addresses = [
    "open-browser-linux-only-despite-stated-cross-platform",
    "no-BROWSER-env-honoured",
    "wait-for-url-timeout-constant-unread-by-template",
    "no-per-request-curl-timeout",
]
new-a2ml-tables = ["browser-launch"]
new-a2ml-keys-in-runtime = [
    "wait-for-url-timeout-env-var",
    "wait-for-url-poll-interval-seconds",
    "wait-for-url-poll-interval-env-var",
    "wait-for-url-per-request-timeout-seconds",
]
status = "open"

[[pr]]
number = 179
branch = "feat/launcher-soft-attach-and-gui-error"
title = "feat(launcher-standard): reference impls for soft-attach + gui-dialog-chain"
class = "reference-impls"
files = [
    "launcher/launcher-standard.a2ml",
    "docs/UX-standards/launcher-standard.adoc",
    "launcher/gui-error.sh",   # new
    "launcher/soft-attach.sh", # new
]
touches-spec-version = false
addresses = [
    "soft-attach-no-reference-impl-drift",
    "gui-dialog-chain-no-reference-impl-drift",
    "soft-attach-when-trigger-underspecified",
]
new-files = ["launcher/gui-error.sh", "launcher/soft-attach.sh"]
new-a2ml-keys-in-error-visibility = ["reference-impl", "suppress-env-var"]
new-a2ml-keys-in-soft-attach = ["reference-impl"]
soft-attach-tools-extended-with = ["style", "trigger"]
status = "open"

[coordination]
# Notes the merger needs to follow.
spec-version-conflict-resolution = """
PRs #170 and #175 both bump [spec].version. They were filed with the
same target (0.3.0); whichever lands second must conflict-resolve to
the next bump (e.g. #175 → 0.4.0 if landing after #170). All other PRs
deliberately did not touch [spec].version to leave the merger free to
sequence.
"""
lockstep-gate-trigger = """
Once #172 merges, PRs #170, #171, #173, #175, #177, #179 will all be
checked by the gate on next push to their branches. All six touch both
launcher-standard.a2ml AND docs/UX-standards/launcher-standard.adoc, so
they pass. #176 (keepopen.sh only) and #172 itself touch neither group
member, so the gate doesn't fire there.
"""

[deferred-followups]
# Items deliberately left out of the 8-PR campaign because they depend
# on merges or live in other repos.

[[deferred]]
id = "extend-lockstep-gate-to-helpers"
description = "Add launcher/resolve-desktop-tools.sh, launcher/gui-error.sh, launcher/soft-attach.sh to the lock-step group in .github/workflows/launcher-standard-lockstep.yml"
gated-on-merges = [171, 179]
estimated-effort = "~10 lines workflow edit"

[[deferred]]
id = "launch-scaffolder-loader-update"
description = "Update the launch-scaffolder Rust loader to consume the new [resolution] / [browser-launch] / [aliases] / [soft-attach].style+trigger / [error-visibility].suppress-env-var tables. Until this lands, this campaign is documentation-and-helper only for the launch-scaffolder consumer — but it already unblocks any new launcher that sources resolve-desktop-tools.sh / gui-error.sh / soft-attach.sh directly."
gated-on-merges = "all-8-merging-or-at-minimum-#171"
repo = "launch-scaffolder"
estimated-effort = "non-trivial — replace hard-coded fallback in Rust loader; respect aliases in mode dispatch; emit style/trigger-aware soft-attach call sites; thread NO_GUI_ERROR into error paths"

[session-lessons-captured]
# Memory entries written during this campaign for future reference.
files = [
    "~/.claude/projects/-home-hyperpolymath/memory/feedback_git_amend_swallows_parallel_staged.md",
    "~/.claude/projects/-home-hyperpolymath/memory/project_standards_launcher_campaign_2026_05_26.md",
]
key-lessons = [
    "git commit --amend without --only sweeps the entire index, including files staged by parallel sessions sharing the checkout. Always use --only <paths> when amending in a multi-session checkout.",
    "When force-pushing to recover from a parallel-session amend incident, use --force-with-lease=branch:explicit-remote-sha; plain --force-with-lease can fail with 'stale info' if local tracking is out of date.",
]

[provenance]
review-method = "manual top-to-bottom read of launcher/ + docs/UX-standards/launcher-standard.adoc + LM-LA-LIFECYCLE-STANDARD.adoc (~2,000 lines)"
review-duration = "single session ~3 hours"
all-prs-gpg-signed = true
all-prs-independent-off-main = true
# Signing-key fingerprint deliberately not recorded inline: gitleaks
# misclassifies the 40-char hex as a generic-api-key. Public PGP
# fingerprints are NOT secrets, but the scanner cannot distinguish
# them from API keys of the same shape. The all-prs-gpg-signed flag
# above is the load-bearing assertion; the fingerprint is recoverable
# from `git log --show-signature` if anyone needs to verify against a
# specific key.
