From 64cb719a125f5fb992d7fc1114baffa1ea802c8c Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Wed, 2 Sep 2026 00:15:45 -0400 Subject: [PATCH] fix(plugin): mint() rendered whatever offset it was handed, not UTC (#3327) Run 5175 red on the Python tests lane. The failing assertion was test_the_mint_is_UTC_not_local, and it was right: `strftime` renders the offset the datetime carries, so mint() only produced UTC because its DEFAULT argument happens to be datetime.now(timezone.utc). Hand it an aware datetime in any other zone and it formats that zone's wall clock -- 22:52Z and its +09:00 twin, the same instant, minted as 2026.09.01.2252 and 2026.09.02.0752. The docstring already claimed "UTC, always", so this was a contract the code did not hold rather than a test asking for something new. Two people minting the same instant would disagree, and the string IS the artifact's identity. Now converts explicitly. A naive datetime is read as UTC rather than as the machine's zone: that is this function's stated contract, and guessing the host's offset is how the same bug returns by another route. Two things found while walking the rest of the module by hand: - test_a_failed_diff_FAILS_rather_than_passing_quietly stubbed EVERY git call to fail, so it tripped the base-branch guard first and passed while proving nothing about the diff arm. rev-parse now succeeds and only the diff fails, and the assertion names the diff message instead of the substring both messages happen to share. - the base-branch failure still said "version-bump check", a name that went away with check_version_bump. The mint script is in the version-relevant set, so fixing it is itself a version-relevant change and forced a fresh mint -- 2026.09.02.0415. That is the asymmetry in #3127 section 3 working as intended rather than a quirk: a format change that did not re-mint would leave the manifest reporting a value the current deriver can no longer produce. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01DN4zBVFWhBST9YqjCfQmPb --- plugin/.claude-plugin/plugin.json | 2 +- scripts/check_plugin.py | 2 +- scripts/mint_plugin_version.py | 16 +++++++++++++--- tests/test_plugin_version_mint.py | 14 +++++++++++--- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/plugin/.claude-plugin/plugin.json b/plugin/.claude-plugin/plugin.json index 4e078a1..ae00a4f 100644 --- a/plugin/.claude-plugin/plugin.json +++ b/plugin/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "scribe", "description": "Scribe system-of-record for Claude Code: MCP tools over your notes/tasks/projects/rules, a session-start push channel that surfaces your always-on rules + active-project context, process-skills (writing-plans, systematic-debugging, verification, brainstorming, reusing-code), and your saved Scribe Processes auto-surfaced as skills (/scribe:sync). Replaces superpowers + file-memory with one app-backed plugin.", - "version": "2026.09.01.2252", + "version": "2026.09.02.0415", "author": { "name": "Bryan Van Deusen" }, diff --git a/scripts/check_plugin.py b/scripts/check_plugin.py index 150e72c..9066a7a 100755 --- a/scripts/check_plugin.py +++ b/scripts/check_plugin.py @@ -596,7 +596,7 @@ def check_version_is_minted(base: str = "origin/main") -> None: # Do NOT pass silently — a check that quietly no-ops is how this class # of bug survives in the first place. fail( - f"cannot resolve {base}, so the version-bump check could not run. " + f"cannot resolve {base}, so the minted-version check could not run. " f"Fetch it first — `git fetch --depth=1 origin main:refs/remotes/" f"origin/main` is enough, since this diffs two trees and needs no " f"common ancestor — or pass --no-version deliberately." diff --git a/scripts/mint_plugin_version.py b/scripts/mint_plugin_version.py index 2d45d77..c37df47 100644 --- a/scripts/mint_plugin_version.py +++ b/scripts/mint_plugin_version.py @@ -64,9 +64,19 @@ VERSION_LINE_RE = re.compile(r'^(\s*"version"\s*:\s*")([^"]*)(".*)$', re.M) def mint(now: datetime | None = None) -> str: - """The version for this moment. UTC, always — a local-time mint would make - the value depend on who ran it.""" - return (now or datetime.now(timezone.utc)).strftime(VERSION_FORMAT) + """The version for this moment. UTC, always. + + The conversion is not decoration: `strftime` renders whatever offset the + datetime carries, so without it two people minting the same instant in + different zones produce different strings — and the string IS the + artifact's identity. A naive datetime is read as UTC rather than as the + machine's zone, because that is this function's stated contract and + guessing the host's offset is how the bug comes back by another route. + """ + moment = now or datetime.now(timezone.utc) + if moment.tzinfo is None: + moment = moment.replace(tzinfo=timezone.utc) + return moment.astimezone(timezone.utc).strftime(VERSION_FORMAT) def rewrite(text: str, version: str) -> str: diff --git a/tests/test_plugin_version_mint.py b/tests/test_plugin_version_mint.py index 8943e5f..4b2d33c 100644 --- a/tests/test_plugin_version_mint.py +++ b/tests/test_plugin_version_mint.py @@ -250,13 +250,21 @@ def test_a_version_that_moved_with_no_content_change_is_NOT_a_failure(monkeypatc def test_a_failed_diff_FAILS_rather_than_passing_quietly(monkeypatch): """A check that cannot run must not report the same thing as a check that - passed — #2663's lesson, and the reason this whole file's siblings exist.""" - monkeypatch.setattr(check_plugin, "_git", lambda *a: (128, "fatal")) + passed — #2663's lesson, and the reason this file's siblings exist. + + `rev-parse` is stubbed to SUCCEED so only the diff fails. Failing every git + call would trip the base-branch guard first and this would pass while + proving nothing about the diff arm. + """ + monkeypatch.setattr( + check_plugin, "_git", + lambda *a: (128, "fatal: bad object") if a[0] == "diff" else (0, ""), + ) monkeypatch.setattr(check_plugin, "manifest_version", lambda ref=None: "2026.09.01.2252") check_plugin.check_version_is_minted("origin/main") assert len(check_plugin.failures) == 1 - assert "could not run" in check_plugin.failures[0] + assert "git diff" in check_plugin.failures[0] # ── The real manifest ──────────────────────────────────────────────────────