From fef01e04ba6183f5bdfef746c19711ff93093528 Mon Sep 17 00:00:00 2001 From: dlxeva Date: Mon, 17 Aug 2026 22:21:16 +0800 Subject: [PATCH 1/4] fix: harden Windows compatibility --- .github/workflows/ci.yml | 28 +++++++ ITERATION_LOG.md | 16 ++++ docs/product/current-state.md | 18 ++++- evals/README.md | 7 ++ ...windows-compatibility-observed-20260817.md | 74 +++++++++++++++++++ scripts/check_current_state.py | 4 +- src/flg/commands/init.py | 10 ++- tests/test_context.py | 3 +- tests/test_current_state_check.py | 10 +++ tests/test_init.py | 35 +++++++++ tests/test_integrity.py | 9 ++- tests/test_smoke_command_resolution.py | 9 ++- tests/test_status.py | 4 +- 13 files changed, 214 insertions(+), 13 deletions(-) create mode 100644 evals/results/windows-compatibility-observed-20260817.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 260f478..a8d16f8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,3 +39,31 @@ jobs: - name: Run smoke test run: python scripts/smoke_test.py + + windows: + name: Windows Python 3.12 + runs-on: windows-latest + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: pip + + - name: Install package and test runner + run: | + python -m pip install --upgrade pip + python -m pip install -e . pytest + + - name: Run test suite + run: pytest -q + + - name: Check current-state freshness + run: python scripts/check_current_state.py + + - name: Run smoke test + run: python scripts/smoke_test.py diff --git a/ITERATION_LOG.md b/ITERATION_LOG.md index 151300e..fcd522f 100644 --- a/ITERATION_LOG.md +++ b/ITERATION_LOG.md @@ -16,6 +16,22 @@ --- +## 2026-08-17:真实 Windows 兼容性报告回灌 + +### FLG-ITER-20260817-01:GBK 首次初始化、MSYS 路径和 Windows 测试链暴露兼容性缺口 [fixing — P0] + +**场景**:一位外部测试者在 Windows 11、PowerShell 5.1 代码页 936、PortableGit Git Bash 和 Python 3.13.14 上,按项目提供的测试任务运行合并提交 `4cd893b`。测试使用真实营销讨论作为 raw session,原始报告和私有素材不进入公开仓库。 + +**观察**:`flg version`、smoke、`C:\` 和 `C:/` session 路径通过;pytest 为 `7 failed, 221 passed`。中文和空格目录已经完成文件写入,但 `init` 打印字面量 checkmark 时触发 GBK `UnicodeEncodeError`。Git Bash `/c/...` 目标没有经过已有路径归一化,被创建到 `C:\c\...`。 + +**逐项复核**:7 个 pytest failure 中,路径显示、`PYTHONPATH` 分隔符、manifest 路径和 Rich 表格宽度属于测试可移植性问题;`current-state` 分类器把 Windows `Path` 字符串与 Git 的 POSIX 路径直接比较,属于真实脚本兼容性缺陷。不能把 7 项统一归为测试错误。 + +**本地候选方向**:首次初始化成功标记改为 GBK 可编码 ASCII;`init --dir` 接入 `normalize_user_path`;修复跨平台断言和 current-state 路径分类;增加 Windows Python 3.12 CI。`doctor` 缺少 repo-map 当前只显示信息,不单独导致 strict 失败,本轮不改变这一产品语义。 + +**本地候选验证**:Windows 定向回归 `10 passed`,全量 `231 passed`,强制 repository source-tree smoke、current-state freshness、workflow YAML 解析与 `git diff --check` 均通过。Windows CI 已加入候选,尚未通过远端 runner 执行。 + +**边界**:本地与 GitHub CI 不能替代原 Windows 主机复测。候选通过后仍只能说明代码与回归闭环完成,现场修复状态保持待验证。 + ## 2026-08-16:中文 owner 范围收拢与外部 Windows 案例收口 ### FLG-ITER-20260816-01:中文 owner 范围收拢在短句与长口述中重复漏提取 [confirmed — P0] diff --git a/docs/product/current-state.md b/docs/product/current-state.md index 539de98..00e2f9c 100644 --- a/docs/product/current-state.md +++ b/docs/product/current-state.md @@ -1,6 +1,6 @@ # FlowGrid Current State -Last updated: 2026-08-12 +Last updated: 2026-08-17 Primary branch: `master` Current code version: `v0.3.0` Current stage: `v0.4 core validation` @@ -28,6 +28,22 @@ Its job is to let project state, boundaries, judgments, progress, and next actio ## Current Verification +- PR #46 merged feedback-driven continuity hardening into `master` at + `4cd893b`; Linux CI passed on Python 3.10, 3.11, and 3.12 with `228` tests. +- A 2026-08-17 external Windows 11 run against `4cd893b` passed installation, + version, smoke, and `C:\` / `C:/` session paths. It observed a GBK `init` + output crash, an MSYS `/c/` init target error, and seven pytest failures. +- The seven failures were reviewed individually: five are platform-sensitive + assertions, while two expose one real Windows path-normalization bug in the + current-state freshness classifier. See the + [bounded compatibility record](../../evals/results/windows-compatibility-observed-20260817.md). +- The current local candidate uses a GBK-safe init success marker, routes + `init --dir` through the shared MSYS path normalizer, makes the affected tests + platform-aware, and adds Windows Python 3.12 CI. It passes `231` local tests, + forced source-tree smoke, current-state freshness, workflow parsing, and diff + checks. A Windows-host rerun remains required before claiming the field + defects fixed. + - PR #44 established the pre-Continuation V2 `master` baseline at merge commit `11c337d` - The independent FlowGrid AML Retriever ranked #8 in the first public Agent diff --git a/evals/README.md b/evals/README.md index cc1f273..3ccf667 100644 --- a/evals/README.md +++ b/evals/README.md @@ -26,6 +26,13 @@ separately in [`case-registry.json`](case-registry.json). The registry records both the supported claim and limitation of every case; it must not label owner-operated evidence as independent external adoption. +The observed Windows 11 compatibility run against merge commit `4cd893b` and +the seven-failure disposition are recorded in +[`results/windows-compatibility-observed-20260817.md`](results/windows-compatibility-observed-20260817.md). +The source report used a real private transcript and remains outside the public +repository; the checked-in result keeps only the technical observations needed +to reproduce and verify the fixes. + ## Evaluation Modes Each scenario should support at least three modes: diff --git a/evals/results/windows-compatibility-observed-20260817.md b/evals/results/windows-compatibility-observed-20260817.md new file mode 100644 index 0000000..177f287 --- /dev/null +++ b/evals/results/windows-compatibility-observed-20260817.md @@ -0,0 +1,74 @@ +# Windows 11 compatibility observation and failure disposition + +Date: 2026-08-17 + +Tested base: `4cd893b5b8198b7e74426fd0c3bde9e3d2e99b92` + +## Evidence boundary + +This record summarizes an externally executed Windows report supplied to the +project owner. The original report and its real marketing transcript remain +outside this repository. They establish observed behavior on one Windows host; +they do not establish user satisfaction, broad Windows compatibility, or a +successful rerun of the candidate fixes. + +Observed environment: + +- Windows 11 Pro, 64-bit, build 26200 +- PowerShell 5.1 with console code page 936 (GBK) +- PortableGit Git Bash +- Python 3.13.14 +- FlowGrid v0.3.0 at merge commit `4cd893b` + +Observed baseline results: + +- `flg version`: passed +- repository smoke test: passed +- full pytest: `7 failed, 221 passed` +- `C:\` and `C:/` session paths: passed +- Chinese and spaced project directory: files were created, then `init` exited + with `UnicodeEncodeError` +- Git Bash `/c/` init target: created under `C:\c\...` instead of the intended + drive root + +## Runtime defects + +| Observation | Assessment | Candidate action | +| --- | --- | --- | +| `init` prints a literal checkmark that code page 936 cannot encode | Runtime defect. The command fails after writing project files. | Use an ASCII first-run success marker and add a GBK-encodable output regression. | +| `init --dir /c/...` resolves to `C:\c\...` | Runtime defect. The existing MSYS normalizer was not used by `init`. | Route `--dir` through `normalize_user_path` and add an init wiring regression. | + +## Seven pytest failures + +The seven failures were reviewed individually. They do not share one cause. + +| Reported failure | Disposition | Reason and action | +| --- | --- | --- | +| Windows path spelling expected `C:/...` but received `C:\...` | Test portability defect | Native `Path` display is platform-specific. Compare with `PureWindowsPath`. | +| Source-tree smoke `PYTHONPATH` expected `:` | Test portability defect | The implementation correctly uses `os.pathsep`; make the assertion do the same. | +| Forced-source smoke `PYTHONPATH` expected `:` | Test portability defect | Same path-separator issue as above. | +| Manifest metadata expected `/` separators | Test portability defect | Validate native `Path.parts` instead of a POSIX suffix string. | +| Status table expected the full `pending_review` cell | Rendering-sensitive test | Rich may truncate a cell at detected terminal width. Fix the test width while retaining the warning and lifecycle assertion. | +| Current-state classifier included its own state document in product drift | Script portability defect | `str(Path(...))` used backslashes on Windows while Git paths use `/`. Normalize Git-style paths in the classifier. | +| Current-state non-product case returned its own state document | Script portability defect | Same classifier defect as above. | + +## Doctor boundary + +The report attributed `doctor --strict` exit 1 to a missing +`.flg/repo-map.json`. Current FlowGrid semantics explicitly keep an unmapped +project compatible: no repo-map is displayed as informational and does not by +itself make strict mode fail. The report does not provide enough machine-readable +doctor output to identify another failing integrity item. This candidate does +not change doctor semantics. + +## Local candidate verification + +- focused Windows regressions: `10 passed` +- full repository suite: `231 passed` +- forced repository source-tree smoke: passed +- current-state freshness check: passed with the state document refresh +- workflow YAML parse and `git diff --check`: passed +- Windows Python 3.12 GitHub Actions job: added, not executed locally + +The original PowerShell and Git Bash commands still need a Windows-host rerun +before the two runtime defects can be described as fixed in the field. diff --git a/scripts/check_current_state.py b/scripts/check_current_state.py index 391a8bf..bcc7b1d 100644 --- a/scripts/check_current_state.py +++ b/scripts/check_current_state.py @@ -32,10 +32,12 @@ def _git(*args: str) -> str: def product_changes_after_state_update(paths: list[str]) -> list[str]: + state_doc_path = STATE_DOC.as_posix() return sorted( path for path in paths - if path != str(STATE_DOC) and path.startswith(PRODUCT_PREFIXES) + if path.replace("\\", "/") != state_doc_path + and path.replace("\\", "/").startswith(PRODUCT_PREFIXES) ) diff --git a/src/flg/commands/init.py b/src/flg/commands/init.py index a7c5a44..2bfcf20 100644 --- a/src/flg/commands/init.py +++ b/src/flg/commands/init.py @@ -10,7 +10,7 @@ from rich.table import Table from rich.text import Text -from ..core.files import ensure_dir, is_flg_project, safe_write +from ..core.files import ensure_dir, is_flg_project, normalize_user_path, safe_write from ..core.i18n import normalize_language from ..core.state import create_initial_state, save_state from .. import templates_en @@ -67,7 +67,7 @@ def init_project( This avoids the common pitfall of initializing in the wrong cwd. """ if directory: - root = Path(directory).expanduser().resolve() + root = normalize_user_path(directory).resolve() root.mkdir(parents=True, exist_ok=True) else: root = Path.cwd() @@ -252,7 +252,11 @@ def init_project( # Display results console.print() - console.print(f"[bold green]✓ FlowGrid project initialized: {project_name}[/bold green]") + # Keep first-run success output encodable by legacy Windows consoles such + # as code page 936. A literal checkmark crashes Rich's legacy renderer + # after the project has already been written, leaving users with a false + # failure and a partially confusing first-run state. + console.print(f"[bold green][OK] FlowGrid project initialized: {project_name}[/bold green]") creation_path = Text("Created in: ", style="dim") creation_path.append(str(root), style="dim") console.print(creation_path, soft_wrap=True) diff --git a/tests/test_context.py b/tests/test_context.py index 1440c0e..bdfa045 100644 --- a/tests/test_context.py +++ b/tests/test_context.py @@ -11,6 +11,7 @@ import os import os.path import json +from pathlib import Path from typer.testing import CliRunner @@ -549,7 +550,7 @@ def test_continuity_manifest_is_compact_and_expands_by_decision_id(tmp_path): assert "flg trace D-002" in manifest assert "long rationale" not in manifest assert ".flg/sessions" not in manifest - assert metadata["path"].endswith(".flg/context/manifest.md") + assert Path(metadata["path"]).parts[-3:] == (".flg", "context", "manifest.md") finally: os.chdir(old_cwd) diff --git a/tests/test_current_state_check.py b/tests/test_current_state_check.py index f4e9d70..4a55e66 100644 --- a/tests/test_current_state_check.py +++ b/tests/test_current_state_check.py @@ -21,3 +21,13 @@ def test_current_state_classifier_ignores_non_product_changes(): assert product_changes_after_state_update( ["README.md", "ITERATION_LOG.md", "docs/product/current-state.md"] ) == [] + + +def test_current_state_classifier_normalizes_windows_git_paths(): + assert product_changes_after_state_update( + [ + "README.md", + "docs\\product\\current-state.md", + "src\\flg\\commands\\init.py", + ] + ) == ["src\\flg\\commands\\init.py"] diff --git a/tests/test_init.py b/tests/test_init.py index 378db7b..b3b5c2e 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -10,6 +10,7 @@ from flg import __version__ from flg.cli import app +from flg.commands import init as init_command runner = CliRunner() @@ -50,6 +51,15 @@ def test_init_creates_project_structure(tmp_dir): assert ".flg/" in (tmp_dir / ".gitignore").read_text(encoding="utf-8") +def test_init_output_is_encodable_by_gbk_console(tmp_dir): + """The first-run success path must not crash a code-page-936 console.""" + result = runner.invoke(app, ["init", "中文项目"]) + + assert result.exit_code == 0 + assert "[OK] FlowGrid project initialized" in result.output + result.output.encode("gbk") + + def test_init_keeps_existing_gitignore_and_adds_flg_privacy_rule(tmp_path): old_cwd = os.getcwd() os.chdir(tmp_path) @@ -243,6 +253,31 @@ def test_init_with_dir_creates_missing_directory(tmp_path): os.chdir(old_cwd) +def test_init_routes_msys_drive_path_through_normalizer(tmp_path, monkeypatch): + """Git Bash /c/ input must reach the normalized native Windows target.""" + old_cwd = os.getcwd() + os.chdir(tmp_path) + try: + target = tmp_path / "msys-target" + seen = [] + + def fake_normalize(value): + seen.append(value) + return target + + monkeypatch.setattr(init_command, "normalize_user_path", fake_normalize) + result = runner.invoke( + app, + ["init", "MSYS Path Test", "--dir", "/c/Users/owner/project"], + ) + + assert result.exit_code == 0 + assert seen == ["/c/Users/owner/project"] + assert (target / ".flg" / "state.json").exists() + finally: + os.chdir(old_cwd) + + def test_init_output_shows_creation_path(tmp_path): """Success output must show where files were created. diff --git a/tests/test_integrity.py b/tests/test_integrity.py index c180922..4844a5d 100644 --- a/tests/test_integrity.py +++ b/tests/test_integrity.py @@ -3,7 +3,7 @@ import json import os import subprocess -from pathlib import Path +from pathlib import Path, PureWindowsPath from typer.testing import CliRunner @@ -450,9 +450,10 @@ def test_doctor_strict_ignores_runtime_identity_when_repo_map_is_absent(tmp_path def test_windows_path_spellings_normalize_for_native_windows_input(): - assert str(normalize_user_path(r"C:\Users\owner\project\session.md", windows=True)) == r"C:\Users\owner\project\session.md" - assert str(normalize_user_path("C:/Users/owner/project/session.md", windows=True)) == "C:/Users/owner/project/session.md" - assert str(normalize_user_path("/c/Users/owner/project/session.md", windows=True)) == "C:/Users/owner/project/session.md" + expected = PureWindowsPath("C:/Users/owner/project/session.md") + assert PureWindowsPath(str(normalize_user_path(r"C:\Users\owner\project\session.md", windows=True))) == expected + assert PureWindowsPath(str(normalize_user_path("C:/Users/owner/project/session.md", windows=True))) == expected + assert PureWindowsPath(str(normalize_user_path("/c/Users/owner/project/session.md", windows=True))) == expected def test_integrity_recognizes_all_supported_windows_path_spellings(tmp_path): diff --git a/tests/test_smoke_command_resolution.py b/tests/test_smoke_command_resolution.py index 4c32100..23278df 100644 --- a/tests/test_smoke_command_resolution.py +++ b/tests/test_smoke_command_resolution.py @@ -1,5 +1,6 @@ """Regression tests for selecting the checkout under test in smoke runs.""" +import os from pathlib import Path from scripts import smoke_test @@ -15,7 +16,9 @@ def test_source_tree_fallback_preserves_dependency_pythonpath(monkeypatch, tmp_p assert command == ["/usr/bin/python3", "-m", "flg.cli"] assert environment == { - "PYTHONPATH": f"{tmp_path / 'src'}:/tmp/dependency-site" + "PYTHONPATH": os.pathsep.join( + (str(tmp_path / "src"), "/tmp/dependency-site") + ) } assert mode == "repository source tree" @@ -30,6 +33,8 @@ def test_forced_source_tree_ignores_sibling_console_script(monkeypatch, tmp_path assert command == ["/venv/bin/python", "-m", "flg.cli"] assert environment == { - "PYTHONPATH": f"{tmp_path / 'src'}:/tmp/dependency-site" + "PYTHONPATH": os.pathsep.join( + (str(tmp_path / "src"), "/tmp/dependency-site") + ) } assert mode == "forced repository source tree" diff --git a/tests/test_status.py b/tests/test_status.py index 607e166..b790081 100644 --- a/tests/test_status.py +++ b/tests/test_status.py @@ -115,7 +115,9 @@ def test_status_warns_on_pending_review_patches(tmp_path): (flg_dir / "state.json").write_text(json.dumps(legacy_state, ensure_ascii=False), encoding="utf-8") (flg_dir / "CONTRACT.md").write_text("# Contract", encoding="utf-8") - result = runner.invoke(app, ["status"]) + # Fix the capture width so Rich's platform-specific terminal detection + # does not truncate the lifecycle value under test. + result = runner.invoke(app, ["status"], terminal_width=160) assert result.exit_code == 0 # Warning must trigger for pending_review patches assert "⚠" in result.output From eebec25fa47d446cefad9b2101b55c90ba8676df Mon Sep 17 00:00:00 2001 From: dlxeva Date: Mon, 17 Aug 2026 22:42:51 +0800 Subject: [PATCH 2/4] ci: run Windows tests in UTF-8 mode --- .github/workflows/ci.yml | 4 ++++ CONTRIBUTING.md | 12 ++++++++++++ ITERATION_LOG.md | 2 ++ docs/product/current-state.md | 6 ++++++ .../windows-compatibility-observed-20260817.md | 7 +++++++ tests/test_status.py | 9 ++++----- 6 files changed, 35 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a8d16f8..56e0b91 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,6 +43,10 @@ jobs: windows: name: Windows Python 3.12 runs-on: windows-latest + # FlowGrid ledgers and repository fixtures are UTF-8. Python otherwise + # falls back to a Windows locale encoding for unqualified test file I/O. + env: + PYTHONUTF8: "1" steps: - name: Check out repository diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6c28a5f..7a95d14 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -41,6 +41,18 @@ pip install -e . pytest -q ``` +FlowGrid ledgers and repository fixtures use UTF-8. Windows contributors should +run Python in UTF-8 mode so unqualified test-only `pathlib` reads and writes use +the same encoding contract: + +```powershell +$env:PYTHONUTF8 = '1' +pytest -q +``` + +The FlowGrid runtime explicitly reads and writes project ledger files as UTF-8; +this environment setting is the repository test-process contract on Windows. + ## Running the smoke test ```bash diff --git a/ITERATION_LOG.md b/ITERATION_LOG.md index fcd522f..73cb3f2 100644 --- a/ITERATION_LOG.md +++ b/ITERATION_LOG.md @@ -30,6 +30,8 @@ **本地候选验证**:Windows 定向回归 `10 passed`,全量 `231 passed`,强制 repository source-tree smoke、current-state freshness、workflow YAML 解析与 `git diff --check` 均通过。Windows CI 已加入候选,尚未通过远端 runner 执行。 +**首次 Windows CI**:`fef01e0` 的 Windows Python 3.12 job 为 `38 failed, 193 passed`。日志逐项显示其中 37 项来自测试和 fixture 的无编码 `pathlib` I/O 在 Windows 默认落到 `cp1252`,另 1 项来自 Rich 表格对 `pending_review` 的平台相关截断。后续候选把 UTF-8 明确为 Windows 测试进程契约,在 job 设置 `PYTHONUTF8=1`,并将 status 回归改为验证 pending warning 语义。 + **边界**:本地与 GitHub CI 不能替代原 Windows 主机复测。候选通过后仍只能说明代码与回归闭环完成,现场修复状态保持待验证。 ## 2026-08-16:中文 owner 范围收拢与外部 Windows 案例收口 diff --git a/docs/product/current-state.md b/docs/product/current-state.md index 00e2f9c..7e9614b 100644 --- a/docs/product/current-state.md +++ b/docs/product/current-state.md @@ -43,6 +43,12 @@ Its job is to let project state, boundaries, judgments, progress, and next actio forced source-tree smoke, current-state freshness, workflow parsing, and diff checks. A Windows-host rerun remains required before claiming the field defects fixed. +- The first Windows CI run at `fef01e0` reported `38 failed, 193 passed`: + 37 failures came from Python defaulting unqualified test and fixture I/O to + `cp1252`, and one came from Rich table-cell truncation. The follow-up candidate + documents UTF-8 as the Windows test-process contract, enables `PYTHONUTF8=1` + for the Windows job, and asserts status warning semantics independently of + table rendering. - PR #44 established the pre-Continuation V2 `master` baseline at merge commit `11c337d` diff --git a/evals/results/windows-compatibility-observed-20260817.md b/evals/results/windows-compatibility-observed-20260817.md index 177f287..84586b1 100644 --- a/evals/results/windows-compatibility-observed-20260817.md +++ b/evals/results/windows-compatibility-observed-20260817.md @@ -70,5 +70,12 @@ not change doctor semantics. - workflow YAML parse and `git diff --check`: passed - Windows Python 3.12 GitHub Actions job: added, not executed locally +The first remote Windows job at `fef01e0` produced `38 failed, 193 passed`. +Log review assigned 37 failures to unqualified test and fixture I/O using the +runner's `cp1252` default, plus one failure to Rich table-cell truncation. The +follow-up defines `PYTHONUTF8=1` as the Windows repository test-process contract +and changes the status regression to assert the warning behavior rather than a +fully rendered table cell. + The original PowerShell and Git Bash commands still need a Windows-host rerun before the two runtime defects can be described as fixed in the field. diff --git a/tests/test_status.py b/tests/test_status.py index b790081..fb15da1 100644 --- a/tests/test_status.py +++ b/tests/test_status.py @@ -115,14 +115,13 @@ def test_status_warns_on_pending_review_patches(tmp_path): (flg_dir / "state.json").write_text(json.dumps(legacy_state, ensure_ascii=False), encoding="utf-8") (flg_dir / "CONTRACT.md").write_text("# Contract", encoding="utf-8") - # Fix the capture width so Rich's platform-specific terminal detection - # does not truncate the lifecycle value under test. - result = runner.invoke(app, ["status"], terminal_width=160) + result = runner.invoke(app, ["status"]) assert result.exit_code == 0 - # Warning must trigger for pending_review patches + # Assert the lifecycle behavior, not Rich's platform-dependent table + # cell rendering, which may truncate the literal status value. assert "⚠" in result.output assert "1 pending patch" in result.output - assert "pending_review" in result.output + assert "needing review" in result.output finally: os.chdir(old_cwd) From ed527d0abd442d53027100937295e5cd32690c8c Mon Sep 17 00:00:00 2001 From: dlxeva Date: Mon, 17 Aug 2026 22:47:03 +0800 Subject: [PATCH 3/4] fix: prevent capture ID collisions --- ITERATION_LOG.md | 2 ++ docs/product/current-state.md | 5 +++++ .../windows-compatibility-observed-20260817.md | 5 +++++ src/flg/commands/capture.py | 7 +++++-- tests/test_capture.py | 13 +++++++++++++ 5 files changed, 30 insertions(+), 2 deletions(-) diff --git a/ITERATION_LOG.md b/ITERATION_LOG.md index 73cb3f2..e613399 100644 --- a/ITERATION_LOG.md +++ b/ITERATION_LOG.md @@ -32,6 +32,8 @@ **首次 Windows CI**:`fef01e0` 的 Windows Python 3.12 job 为 `38 failed, 193 passed`。日志逐项显示其中 37 项来自测试和 fixture 的无编码 `pathlib` I/O 在 Windows 默认落到 `cp1252`,另 1 项来自 Rich 表格对 `pending_review` 的平台相关截断。后续候选把 UTF-8 明确为 Windows 测试进程契约,在 job 设置 `PYTHONUTF8=1`,并将 status 回归改为验证 pending warning 语义。 +**第二次 Windows CI**:UTF-8 修复将结果收敛到 `1 failed, 230 passed`。残余失败揭示 BIZ 批量导入在 Windows 同一时钟刻度内生成相同 capture ID,后一条覆盖前一条。候选保留原有 ID 形态,改用独立 UUID 后缀,并增加冻结时钟下的唯一性回归。 + **边界**:本地与 GitHub CI 不能替代原 Windows 主机复测。候选通过后仍只能说明代码与回归闭环完成,现场修复状态保持待验证。 ## 2026-08-16:中文 owner 范围收拢与外部 Windows 案例收口 diff --git a/docs/product/current-state.md b/docs/product/current-state.md index 7e9614b..41f2d47 100644 --- a/docs/product/current-state.md +++ b/docs/product/current-state.md @@ -49,6 +49,11 @@ Its job is to let project state, boundaries, judgments, progress, and next actio documents UTF-8 as the Windows test-process contract, enables `PYTHONUTF8=1` for the Windows job, and asserts status warning semantics independently of table rendering. +- The UTF-8 follow-up reduced Windows CI to `1 failed, 230 passed`. The remaining + failure exposed a real same-timestamp capture-ID collision during a multi-item + BIZ import on Windows. The next candidate preserves the `cap-...-xxxxxx` ID + shape while replacing the timestamp-derived suffix with an independent UUID + suffix and adds a frozen-clock uniqueness regression. - PR #44 established the pre-Continuation V2 `master` baseline at merge commit `11c337d` diff --git a/evals/results/windows-compatibility-observed-20260817.md b/evals/results/windows-compatibility-observed-20260817.md index 84586b1..3943f73 100644 --- a/evals/results/windows-compatibility-observed-20260817.md +++ b/evals/results/windows-compatibility-observed-20260817.md @@ -77,5 +77,10 @@ follow-up defines `PYTHONUTF8=1` as the Windows repository test-process contract and changes the status regression to assert the warning behavior rather than a fully rendered table cell. +That follow-up reduced the remote result to `1 failed, 230 passed`. The remaining +failure exposed a same-timestamp capture-ID collision in a two-item BIZ import +on Windows. The next candidate replaces the timestamp-derived six-character +suffix with an independent UUID suffix and adds a frozen-clock uniqueness test. + The original PowerShell and Git Bash commands still need a Windows-host rerun before the two runtime defects can be described as fixed in the field. diff --git a/src/flg/commands/capture.py b/src/flg/commands/capture.py index 4aa6c09..01b2f4e 100644 --- a/src/flg/commands/capture.py +++ b/src/flg/commands/capture.py @@ -2,9 +2,9 @@ from __future__ import annotations -import hashlib import json import re +import uuid from datetime import datetime from pathlib import Path from typing import Optional @@ -40,7 +40,10 @@ def _ensure_captures_dir(root: Path) -> Path: def _generate_id() -> str: now = datetime.now() ts = now.strftime("%Y%m%d-%H%M%S") - rand = hashlib.md5(str(now.timestamp()).encode()).hexdigest()[:6] + # Windows wall-clock resolution can return the same timestamp for multiple + # captures created in one import loop. Keep the public ID shape while using + # an independent random suffix so one judgment cannot overwrite another. + rand = uuid.uuid4().hex[:6] return f"cap-{ts}-{rand}" diff --git a/tests/test_capture.py b/tests/test_capture.py index 8c8c837..392f8d3 100644 --- a/tests/test_capture.py +++ b/tests/test_capture.py @@ -2,15 +2,28 @@ import json import os +from datetime import datetime as RealDateTime from typer.testing import CliRunner from flg.cli import app +from flg.commands import capture as capture_command runner = CliRunner() +def test_capture_ids_remain_unique_when_wall_clock_is_unchanged(monkeypatch): + class FrozenDateTime: + @classmethod + def now(cls): + return RealDateTime(2026, 8, 17, 14, 44, 8) + + monkeypatch.setattr(capture_command, "datetime", FrozenDateTime) + + assert capture_command._generate_id() != capture_command._generate_id() + + def test_auto_confirm_keeps_inferred_capture_pending(tmp_path): old_cwd = os.getcwd() os.chdir(tmp_path) From f557df337db19bcf7e05724458583add8c762df8 Mon Sep 17 00:00:00 2001 From: dlxeva Date: Tue, 18 Aug 2026 13:48:26 +0800 Subject: [PATCH 4/4] docs: record Windows field rerun --- ITERATION_LOG.md | 6 ++-- docs/product/current-state.md | 16 +++++++---- ...windows-compatibility-observed-20260817.md | 28 ++++++++++++++++--- 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/ITERATION_LOG.md b/ITERATION_LOG.md index e613399..caa7f7f 100644 --- a/ITERATION_LOG.md +++ b/ITERATION_LOG.md @@ -18,7 +18,7 @@ ## 2026-08-17:真实 Windows 兼容性报告回灌 -### FLG-ITER-20260817-01:GBK 首次初始化、MSYS 路径和 Windows 测试链暴露兼容性缺口 [fixing — P0] +### FLG-ITER-20260817-01:GBK 首次初始化、MSYS 路径和 Windows 测试链暴露兼容性缺口 [fixed — P0] **场景**:一位外部测试者在 Windows 11、PowerShell 5.1 代码页 936、PortableGit Git Bash 和 Python 3.13.14 上,按项目提供的测试任务运行合并提交 `4cd893b`。测试使用真实营销讨论作为 raw session,原始报告和私有素材不进入公开仓库。 @@ -34,7 +34,9 @@ **第二次 Windows CI**:UTF-8 修复将结果收敛到 `1 failed, 230 passed`。残余失败揭示 BIZ 批量导入在 Windows 同一时钟刻度内生成相同 capture ID,后一条覆盖前一条。候选保留原有 ID 形态,改用独立 UUID 后缀,并增加冻结时钟下的唯一性回归。 -**边界**:本地与 GitHub CI 不能替代原 Windows 主机复测。候选通过后仍只能说明代码与回归闭环完成,现场修复状态保持待验证。 +**现场复测**:外部测试者在同一 Windows 11 环境特征下,以独立虚拟环境复测固定提交 `ed527d0abd442d53027100937295e5cd32690c8c`。PowerShell 5.1 代码页 936 初始化中文空格目录退出码为 0、无 `UnicodeEncodeError`;Git Bash `/c/Users/...` 正确落到 `C:\Users\...`,进入目标目录后 `flg status` 能识别项目。全量测试记录为 `232 passed / 0 failed / 0 errors`,smoke 通过。 + +**收口**:两个原始运行时缺陷已在报告主机和固定提交上闭环,PR #47 的 Linux 与 Windows CI 也全部通过。结论保持有界:这是单一外部 Windows 主机的直接修复证据,不代表全部 Windows 环境,也不推导用户满意度或产品采用情况。 ## 2026-08-16:中文 owner 范围收拢与外部 Windows 案例收口 diff --git a/docs/product/current-state.md b/docs/product/current-state.md index 41f2d47..1ca20f2 100644 --- a/docs/product/current-state.md +++ b/docs/product/current-state.md @@ -37,12 +37,10 @@ Its job is to let project state, boundaries, judgments, progress, and next actio assertions, while two expose one real Windows path-normalization bug in the current-state freshness classifier. See the [bounded compatibility record](../../evals/results/windows-compatibility-observed-20260817.md). -- The current local candidate uses a GBK-safe init success marker, routes +- The current PR #47 candidate uses a GBK-safe init success marker, routes `init --dir` through the shared MSYS path normalizer, makes the affected tests - platform-aware, and adds Windows Python 3.12 CI. It passes `231` local tests, - forced source-tree smoke, current-state freshness, workflow parsing, and diff - checks. A Windows-host rerun remains required before claiming the field - defects fixed. + platform-aware, and adds Windows Python 3.12 CI. GitHub Actions passes on + Python 3.10, 3.11, 3.12, and Windows Python 3.12. - The first Windows CI run at `fef01e0` reported `38 failed, 193 passed`: 37 failures came from Python defaulting unqualified test and fixture I/O to `cp1252`, and one came from Rich table-cell truncation. The follow-up candidate @@ -54,6 +52,14 @@ Its job is to let project state, boundaries, judgments, progress, and next actio BIZ import on Windows. The next candidate preserves the `cap-...-xxxxxx` ID shape while replacing the timestamp-derived suffix with an independent UUID suffix and adds a frozen-clock uniqueness regression. +- A same-host field rerun against fixed commit `ed527d0` then passed the two + original runtime scenarios: PowerShell 5.1 at code page 936 initialized a + Chinese, spaced path without an encoding crash, and Git Bash mapped an + explicit `/c/Users/...` target to `C:\Users\...` rather than + `C:\c\Users\...`. The rerun also reported `232` tests with zero failures + or errors and a passing repository smoke test. This closes the reported + defects for that host and fixed commit; it does not establish universal + Windows compatibility. - PR #44 established the pre-Continuation V2 `master` baseline at merge commit `11c337d` diff --git a/evals/results/windows-compatibility-observed-20260817.md b/evals/results/windows-compatibility-observed-20260817.md index 3943f73..24d0b12 100644 --- a/evals/results/windows-compatibility-observed-20260817.md +++ b/evals/results/windows-compatibility-observed-20260817.md @@ -9,8 +9,9 @@ Tested base: `4cd893b5b8198b7e74426fd0c3bde9e3d2e99b92` This record summarizes an externally executed Windows report supplied to the project owner. The original report and its real marketing transcript remain outside this repository. They establish observed behavior on one Windows host; -they do not establish user satisfaction, broad Windows compatibility, or a -successful rerun of the candidate fixes. +they do not establish user satisfaction or broad Windows compatibility. A +second report from the same host reran the bounded compatibility scenarios +against candidate commit `ed527d0abd442d53027100937295e5cd32690c8c`. Observed environment: @@ -82,5 +83,24 @@ failure exposed a same-timestamp capture-ID collision in a two-item BIZ import on Windows. The next candidate replaces the timestamp-derived six-character suffix with an independent UUID suffix and adds a frozen-clock uniqueness test. -The original PowerShell and Git Bash commands still need a Windows-host rerun -before the two runtime defects can be described as fixed in the field. +## Windows-host candidate rerun + +On 2026-08-17, the external tester reran the bounded scenarios against fixed +candidate commit `ed527d0abd442d53027100937295e5cd32690c8c` in a fresh Python +3.13.14 virtual environment on the same Windows 11 host characteristics. + +- PowerShell 5.1 at code page 936 initialized a Chinese, spaced project path + with exit code 0, no `UnicodeEncodeError`, and an intact `.flg` directory. +- Git Bash initialized an explicit `/c/Users/...` target under the intended + `C:\Users\...` path, not `C:\c\Users\...`; `flg status` then recognized + the project. +- The repository suite reported `232` tests, `0` failures, and `0` errors after + PortableGit was made available to subprocesses. +- The repository smoke test ended with `Smoke test passed.` +- `doctor --strict` still reported `Needs attention` for the temporary project + because no runtime identity was configured. That observation is outside the + two compatibility acceptance criteria and does not change doctor semantics. + +This rerun is direct field evidence that the two reported runtime defects are +fixed for the tested host and commit. It remains one-host evidence, not a claim +of universal Windows compatibility.