Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/sources.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ repository that ships the dotfiles. Gentleman.Dots is hosted at
| Upstream | Kind | Verified remote (HTTPS) | Pinned ref | Status |
| --- | --- | --- | --- | --- |
| ML4W (Hyprland desktop dotfiles) | Desktop base environment | <https://github.com/mylinuxforwork/dotfiles.git> | `3960570f47f4f691c424ff46b387d389a8e69bca` (tag `2.16`) | Pinned — verified against tag 2.16 and remote HEAD |
| Gentleman.Dots | Shell / editor / terminal base configuration | <https://github.com/Gentleman-Programming/Gentleman.Dots.git> | `6f44b797b016aea92772d8a6d81a5f1bc53a84bb` | Pinned — verified against remote HEAD |
| Gentleman.Dots | Shell / editor / terminal base configuration | <https://github.com/Gentleman-Programming/Gentleman.Dots.git> | `5b13e07b5a6fde799b65953e92cec323f684408f` | Pinned — verified against remote HEAD |

### Pin mechanism

Expand Down
8 changes: 4 additions & 4 deletions docs/upstream-manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"version": 1,
"provenance": {
"verified_on": "2026-09-28T05:42:13Z",
"verified_on": "2026-09-30T07:27:46Z",
"method": "Resolved each upstream HTTPS remote with git ls-remote and recorded the exact returned commit. ML4W is pinned to tag 2.16 (lightweight tag, 3960570f47f4f691c424ff46b387d389a8e69bca), which was also the remote HEAD at verification; Gentleman.Dots is pinned to main HEAD. No ref was ever filled without verification.",
"command": "git ls-remote https://github.com/mylinuxforwork/dotfiles.git HEAD refs/tags/2.16; git ls-remote https://github.com/Gentleman-Programming/Gentleman.Dots.git HEAD refs/heads/main"
},
Expand All @@ -11,14 +11,14 @@
"url": "https://github.com/mylinuxforwork/dotfiles.git",
"status": "pinned",
"pinned_ref": "3960570f47f4f691c424ff46b387d389a8e69bca",
"verified_on": "2026-09-28T05:42:13Z"
"verified_on": "2026-09-30T07:27:46Z"
},
"gentleman-dots": {
"name": "Gentleman.Dots (shell / editor / terminal base configuration)",
"url": "https://github.com/Gentleman-Programming/Gentleman.Dots.git",
"status": "pinned",
"pinned_ref": "6f44b797b016aea92772d8a6d81a5f1bc53a84bb",
"verified_on": "2026-09-28T05:42:13Z"
"pinned_ref": "5b13e07b5a6fde799b65953e92cec323f684408f",
"verified_on": "2026-09-30T07:27:46Z"
}
},
"owned_paths": {}
Expand Down
50 changes: 50 additions & 0 deletions odd/tasks/upstream-bump-2026-09-30.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Upstream bump 2026-09-30 (Gentleman.Dots pin, pin check, Herdr 0.9.3)

## Objective

Keep the pinned upstreams and the installed tools current ("vanguardia") without breaking the
Dreamcoder overlay: verify what changed upstream before bumping, and record the evidence.

## Findings (checked 2026-09-30)

- ML4W: latest tag `2.16` (pinned). Remote `HEAD` is now `328351d`, ahead of the tag with
unreleased commits; the project pins releases, so this is reported as drift only.
- Gentleman.Dots: one new commit `5b13e07` (`fix(nvim): use valid snacks.picker name for
obsidian`, 2 lines in `GentlemanNvim/nvim/lua/plugins/obsidian.lua`). `DreamcoderNvim` does
`dofile` of Gentleman's `init.lua`, so the fix reaches Neovim once the local checkout moves.
- Herdr: latest `v0.9.3` (2026-09-29), installed `0.9.1`. The official release asset validates
the repo's 0.9.1 dark and light variants with `herdr config check` (`config: ok`).
- `scripts/upstream-diff.py --check-pins` could not verify any pin that was no longer the remote
`HEAD`: it ran `git ls-remote <url> <sha>`, which matches ref names, never hashes, so it always
exited 2. It went unnoticed while every pin equalled `HEAD`.

## Constraints

- Never install or restart herdr while the user's server has open panes; use a scratch copy of
the release asset for evidence.
- Commit messages: lowercase subject, body lines <= 100 (commitlint). One PR per work unit.
- TDD: off (no project configuration); each behaviour change has a test that fails without it.

## Tasks

- [x] U1 — Pin Gentleman.Dots to `5b13e07` (manifest, sources table, test fixture) and fast-forward
the local `~/Gentleman.Dots` checkout. Route: inline.
- [x] U2 — `--check-pins` verifies reachability by fetching the pinned hash into a throwaway bare
repo (the mechanism `_diff_upstream` already uses); tests forbid the ref-name lookup. Live
result: Gentleman.Dots current, ML4W stale (drift, pin reachable). Route: inline.
- [ ] U3 — Herdr 0.9.3 profile and evidence, generated variants, docs. Route: delegated writer
(separate PR).

## Acceptance criteria

- `python3 scripts/upstream-diff.py --check-pins` exits 0 against the real remotes and reports
Gentleman.Dots current and ML4W stale-but-reachable.
- pytest, bats, ruff, mypy green; `verify-repo-sync.py` ok.

## Progress

- U1 and U2 verified: pytest exit 0, bats 192 ok, ruff and mypy clean.

## Next step

U3 (agent running in its own worktree), then record its evidence here.
32 changes: 28 additions & 4 deletions scripts/upstream-diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,33 @@ def _unpinned_result(upstream: dict[str, Any]) -> dict[str, Any]:
}


def _require_pin_fetchable(name: str, url: str, pin: str) -> None:
"""Fail closed unless the pinned commit can still be fetched by hash.

`git ls-remote <url> <sha>` cannot do this: it matches ref names, never hashes, so it
exits 2 for every pin that is no longer the remote HEAD. Fetching the hash into a
throwaway bare repo (as _diff_upstream does) is the check that reflects reality.
"""
temp_dir: Path | None = None
try:
temp_dir = Path(
tempfile.mkdtemp(prefix="dreamcoder-upstream-pin-", dir=str(_system_temp_base()))
)
_run_git(["git", "init", "--bare", "--quiet", str(temp_dir)], ROOT, GIT_TIMEOUT)
_run_git(
["git", "-C", str(temp_dir), "fetch", "--no-tags", "--depth=1", url, pin],
ROOT,
FETCH_TIMEOUT,
)
except GitError as exc:
raise GitError(
f"pinned ref {pin} for {name} is no longer reachable on the remote: {exc}"
) from exc
finally:
if temp_dir is not None:
shutil.rmtree(temp_dir, ignore_errors=True)


def _check_pin(name: str, upstream: dict[str, Any]) -> dict[str, Any]:
"""Verify the pinned ref against the remote HEAD (drift is report-only)."""
url = upstream["url"]
Expand All @@ -297,10 +324,7 @@ def _check_pin(name: str, upstream: dict[str, Any]) -> dict[str, Any]:
"note": "pinned ref matches remote HEAD",
}
if head != pin:
reach_out = _run_git(["git", "ls-remote", "--exit-code", url, pin], ROOT, LSREMOTE_TIMEOUT)
reach = reach_out.decode("utf-8", errors="replace").split("\t", 1)[0].strip()
if not SHA_REF_RE.match(reach):
raise GitError(f"pinned ref {pin} for {name} is no longer reachable on the remote")
_require_pin_fetchable(name, url, pin)
result["status"] = "stale"
result["note"] = (
f"remote HEAD {head} differs from pinned ref {pin}; pin still reachable — "
Expand Down
28 changes: 24 additions & 4 deletions tests/test_upstream_diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -438,11 +438,19 @@ def test_check_pins_current(env, capsys):
assert len(fake.calls) == 1


def _reachable_pin_git(module: ModuleType) -> FakeGit:
"""HEAD moved past the pin; the pinned commit is still fetchable by hash."""
fake = FakeGit(module)
fake.when(lambda a: "ls-remote" in a and a[-1] == "HEAD", f"{HEAD}\tHEAD\n".encode())
fake.when(lambda a: "init" in a, b"")
fake.when(lambda a: "fetch" in a and a[-1] == PIN, b"")
fake.when(lambda a: "cat-file" in a and "-e" in a, b"")
return fake


def test_check_pins_stale_reports_drift(env, capsys):
_write_manifest(env, _manifest())
fake = FakeGit(env.module)
fake.when(lambda a: "ls-remote" in a and a[-1] == "HEAD", f"{HEAD}\tHEAD\n".encode())
fake.when(lambda a: "ls-remote" in a and a[-1] == PIN, f"{PIN}\trefs/heads/main\n".encode())
fake = _reachable_pin_git(env.module)
_stub_git(env.module, fake)
assert env.module.main(["--check-pins", "--json"]) == 0 # drift is report-only
report = json.loads(capsys.readouterr().out)
Expand All @@ -452,11 +460,23 @@ def test_check_pins_stale_reports_drift(env, capsys):
assert "drift" in report["upstreams"]["ml4w"]["note"]


def test_check_pins_never_looks_a_hash_up_as_a_ref_name(env, capsys):
# `git ls-remote <url> <sha>` matches ref NAMES, never hashes, so it always exits 2 for a
# pin that is no longer the remote HEAD. Reachability must be checked by fetching the hash.
_write_manifest(env, _manifest())
fake = _reachable_pin_git(env.module)
_stub_git(env.module, fake)
assert env.module.main(["--check-pins", "--json"]) == 0
assert not any("ls-remote" in call and call[-1] == PIN for call in fake.calls)
assert any("fetch" in call and call[-1] == PIN for call in fake.calls)


def test_check_pins_unreachable_pin_fails_closed(env, capsys):
_write_manifest(env, _manifest())
fake = FakeGit(env.module)
fake.when(lambda a: "ls-remote" in a and a[-1] == "HEAD", f"{HEAD}\tHEAD\n".encode())
fake.when(lambda a: "ls-remote" in a and a[-1] == PIN, b"") # pin no longer advertised
fake.when(lambda a: "init" in a, b"")
fake.when(lambda a: "fetch" in a, env.module.GitError("upload-pack: not our ref"))
_stub_git(env.module, fake)
assert env.module.main(["--check-pins", "--json"]) == 1
captured = capsys.readouterr()
Expand Down
2 changes: 1 addition & 1 deletion tests/test_verify_repo_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ def _base_manifest() -> dict:
"name": "Gentleman.Dots (shell / editor / terminal base configuration)",
"url": "https://github.com/Gentleman-Programming/Gentleman.Dots.git",
"status": "pinned",
"pinned_ref": "6f44b797b016aea92772d8a6d81a5f1bc53a84bb",
"pinned_ref": "5b13e07b5a6fde799b65953e92cec323f684408f",
"verified_on": "2026-08-10T01:27:49Z",
},
},
Expand Down
Loading