Every ticket as the root of its provenance graph — goal, patch, approval, runs and (as the ticket-class build lands) mandate, manifestations, guarantees, journey, artifacts and children. Rendered through the <<<TicketGraphList>>> tag.
goal
THIS UNIT SUPERSEDES #294 — it is the SAME module and the same work, re-filed to be
SELF-CONTAINED. The superseded unit named its source only as a `git cat-file` command
against the forge's bare repos, which the sandboxed pair_loop worker that executes these
tickets cannot run, so it could not be done as filed (#346; #259 is the proof). A ticket body
is permanent in SQL (tg_tickets_immutable_identity, migrations/0029_ticket_authorization
.sql:111-117), so that body could not be repaired — this ticket carries the fix instead, and
the original is halted by a stop ticket rather than erased. Do the work HERE.
Rewrite ONE unique forge module as entirely new, behavior-preserving, installable,
control-config'd Python, per the FORGE-wide refactor standard #200 (#std).
This ticket covers ONE module — the one identified below — and nothing else. It was filed
against the module's DEDUPLICATED identity, so if that module was copy-pasted across the
forge, every copy is covered by this single ticket.
MODULE KEY : forge-rw:8968b64101a54fde
(the ticketer's idempotency marker — it lives in this ticket's TITLE. Do
not edit the title, or the module will be re-filed as a duplicate.)
IDENTITY : AST-NORMALIZED — codebean's `_ast_norm` (#183), i.e.
ast.dump(ast.parse(ast.unparse(ast.parse(src)))).
Copies of this module that differ only in comments, blank lines,
quoting or spacing carry the SAME module-sha and are covered by THIS
ticket. A differing docstring is real content, so a module whose
docstring differs is a DIFFERENT module with its own ticket.
SIZE : 3106 bytes
EXTENSION : .py
CANONICAL SOURCE (the location this ticket is named for):
repo : code__code-invalid__coupler
path : coupler/updater.py
ref : refs/heads/stable (as recorded when this module was first filed)
blob : 3800ae29cb412ff82b508a1493649e900074fbe0 (git blob sha1)
REFERENCE ONLY — you do NOT need this command. This module's bytes are embedded
COMPLETE in THE ORIGINAL SOURCE block below, and that is what the rewrite is based
on. For a reader who does have forge access:
git --git-dir=/Users/stevenpeterson/code/installed/jazz-project/config/forge/remotes/code__code-invalid__coupler.git cat-file blob 3800ae29cb412ff82b508a1493649e900074fbe0
THE SAME MODULE ALSO LIVES AT THESE FORGE TIPS — 2 copy/copies. They SHARE THIS
ticket: this module is rewritten ONCE, and every location below is satisfied by
that one rewrite. Do not file a ticket per copy.
coupler:coupler/updater.py
gitlab__testmonkeyalpha__coupler:coupler/updater.py
HISTORICAL OCCURRENCES of this exact blob — every OTHER DISTINCT {repo, path}
the omni-git index (`occ`, joined on the git blob sha1) records this
byte-identical file at, with the index's own `ts` for the first row of each.
Any place this run enumerated LIVE is listed under the forge tips above, not
here. Context for the rewrite, not extra work:
testmonkeyalpha/coupler:coupler/updater.py [occ.ts gitlab]
THE ORIGINAL SOURCE, EMBEDDED — YOU NEED NO FORGE ACCESS TO DO THIS TICKET.
The module's own bytes are reproduced below, inside this ticket. THEY are the
authoritative copy and the behavior-preserving basis: rewrite from them. The
`git --git-dir=... cat-file blob` command under CANONICAL SOURCE above is kept only as a
REFERENCE for a reader who happens to have forge access — running it is NOT part of this
ticket. (Why the bytes are here at all: the pair_loop worker that executes this ticket is
sandboxed to the ticket's own congruency worktree and cannot read the forge's bare repos.
Ticket #346 is the fix that put them on the ticket.)
The block BEGINS at the line
===ORIGINAL SOURCE (verbatim, the behavior-preserving basis)===
and ENDS at the first line
===END SOURCE===
Everything strictly between those two lines is the module — 3106 byte(s), byte for byte, with no re-indentation, no re-wrapping and nothing
elided.
===ORIGINAL SOURCE (verbatim, the behavior-preserving basis)===
#!/usr/bin/env python3
"""
coupler/updater.py — pull and reinstall when a new git tag lands on stable.
Run by launchd on a timer. Compares the locally installed version against the
latest tag reachable from origin/stable. If they differ, pulls, reinstalls,
and kicks the server. The bridge reconnects automatically — no downtime.
"""
import json
import logging
import os
import subprocess
import sys
from pathlib import Path
REPO = Path(__file__).parent.parent # ~/code/coupler
PYTHON = sys.executable
PIP = [PYTHON, "-m", "pip"]
LAUNCHCTL = "/bin/launchctl"
SERVICE = "com.coupler.server"
LOG_FILE = Path.home() / "Library/Logs/coupler-updater.log"
logging.basicConfig(
filename=str(LOG_FILE),
level=logging.INFO,
format="%(asctime)s %(levelname)s %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
log = logging.getLogger("coupler.updater")
def _run(cmd: list, **kw) -> subprocess.CompletedProcess:
return subprocess.run(cmd, capture_output=True, text=True, cwd=REPO, **kw)
def _current_version() -> str:
"""Version the running package reports (from git describe or importlib)."""
r = _run(["git", "describe", "--tags", "--abbrev=0"])
if r.returncode == 0:
return r.stdout.strip().lstrip("v")
# fall back to importlib if no local tag
from importlib.metadata import version
return version("coupler")
def _remote_version() -> str | None:
"""Latest tag reachable from origin/stable after a fetch."""
r = _run(["git", "fetch", "--tags", "origin", "stable"])
if r.returncode != 0:
log.warning("git fetch failed: %s", r.stderr.strip())
return None
r = _run(["git", "describe", "--tags", "--abbrev=0", "origin/stable"])
if r.returncode != 0:
log.debug("no remote tag yet: %s", r.stderr.strip())
return None
return r.stdout.strip().lstrip("v")
def _pull_and_install() -> bool:
for cmd, label in [
(["git", "pull", "--ff-only", "origin", "stable"], "git pull"),
(PIP + ["install", "-e", ".", "-q"], "pip install"),
]:
r = _run(cmd)
if r.returncode != 0:
log.error("%s failed:\n%s", label, r.stderr.strip())
return False
log.info("%s ok", label)
return True
def _restart_server():
uid = os.getuid()
r = subprocess.run(
[LAUNCHCTL, "kickstart", "-k", f"gui/{uid}/{SERVICE}"],
capture_output=True, text=True,
)
if r.returncode == 0:
log.info("server restarted")
else:
log.error("kickstart failed: %s", r.stderr.strip())
def main():
log.debug("update check")
remote = _remote_version()
if remote is None:
return
current = _current_version()
if current == remote:
log.debug("up to date (%s)", current)
return
log.info("update available: %s → %s", current, remote)
if not _pull_and_install():
log.error("update failed — server not restarted")
return
log.info("updated to %s", remote)
_restart_server()
if __name__ == "__main__":
main()
===END SOURCE===
THE CALLABLE SURFACE — every callable in the original, lowered with codebean's
`lower_node` (#183). The rewrite must present THIS surface (same names, same
parameters, same defaults) so callers of the original keep working:
_run(cmd: list, **kw) -> subprocess.CompletedProcess
_current_version() -> str
_remote_version() -> str | None
_pull_and_install() -> bool
_restart_server()
main()
CAPTURED BEHAVIORAL EXEMPLARS: none. no function in this module is minable — criteria_miner mines only
self-contained pure functions (it refuses anything that mentions a
world-touching or non-deterministic name, plus methods and no-arg
functions), and nothing here qualified
This does NOT weaken the ticket's bar — it MOVES the work: you must write the
equivalence exemplars by hand from the original's behavior before rewriting, and
they are what settles this ticket.
THE STANDARD — #200's four requirements, which this ticket exists to enforce. All four are
the standard's own words; none is optional:
1. BEHAVIOR-PRESERVING, VERIFIED NOT ASSERTED — the deliverable is ENTIRELY NEW source
code (rewritten, not copied). It must reproduce the ORIGINAL's behavior, and the proof
is the exemplars above (captured via codebean lower_* #183 + criteria_miner
IO-exemplars, exactly as #200 specifies).
2. A PYTHON MODULE — regardless of the source language. This module's source is `.py`;
the rewrite is Python either way.
3. INSTALLABLE — a proper package: pyproject.toml, `pip install -e .` works, console
entry points where applicable.
4. CONTROL-CONFIG'D — NO hardcoded paths or params. A JSON/registry config object drives
it (the install.json / registry.json idiom of this repo), and per note-for-claude a
tool that cannot see its config THROWS rather than guessing a default.
ACCEPTANCE (#200's, per rewrite ticket) — this ticket settles when ALL of:
a. the new source reproduces every captured exemplar above, plus the edge cases you add
for what sampling cannot reach (paste the runs as evidence);
b. it presents the callable surface above, so existing callers keep working;
c. `pip install -e .` installs it clean from its own pyproject.toml;
d. it runs off a config object with ZERO hardcoded settings, and throws without one.
Kind is `build` (a pair: coder + adversarial tester). This ticket hangs off #200 by a
`spawned` edge, so a stop ticket aimed at #200 brakes every rewrite in this programme.
Filed mechanically by checkouts/current/congruency/tools/forge_refactor_ticketer.py under ticket #201, from the forge bare repos at
/Users/stevenpeterson/code/installed/jazz-project/config/forge/remotes. Its original source is embedded above (#346), so this ticket is complete on its own.