Skip to content

fix(beast_power): close unit-test holes — non-finite config, SoC guards, literal headers - #190

Merged
Coldaine merged 4 commits into
mainfrom
fix/beast-power-unit-holes
Aug 8, 2026
Merged

fix(beast_power): close unit-test holes — non-finite config, SoC guards, literal headers#190
Coldaine merged 4 commits into
mainfrom
fix/beast-power-unit-holes

Conversation

@Coldaine

@Coldaine Coldaine commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

User description

Phase 1 of the beast_power test-improvement plan

3 commits, all green on Windows (90 passed, 5 skipped) and CI-safe on ubuntu (same suite, no ROS).

1. Non-finite DurableCsvWriter config rejection (logging_core.py)

max_bytes=NaN/inf and fsync_every_n=NaN/inf slipped past the bare <= 0 checks (NaN comparisons are False), so rotation never fired (silent disk fill) and fsync never ran (zero durability). Now rejected with ValueError.

  • Tests: 4 new parametrized rejection cases (max_bytes NaN/inf, fsync_every_n NaN/inf) — each fails without the fix.

2. voltage_to_soc non-finite guard (soc.py)

+inf clamped to 1.0 ("100 % battery") and -inf to 0.0 on garbage input; only NaN was guarded. Any non-finite volts is a failed measurement → returns NaN.

  • Tests: 3 new cases (+inf, -inf, NaN) assert math.isnan — all 3 fail without the fix.

3. Parametrized full OCV table test (test_soc_curve.py)

All 12 knots written out literally (independent of _3S_OCV_SOC), exact knot voltage → exact knot SOC, plus an assertion the table is strictly sorted ascending and has exactly 12 entries — a silent table edit (value, count, or order) now fails.

4. legacy_fake_percentage(NaN) pin

Pins current behavior (NaN in → NaN out) so a future "fix" that clamps to 0/1 is caught.

5. Charging-detection edge coverage (test_sign_convention.py)

  • is_charging(NaN current, 0.05) must not claim charging.
  • build_telemetry FULL (≥full_soc while charging) and NOT_CHARGING (idle current in deadband, and NaN current) status branches asserted exactly.

6. Absent-sensor file surgery (test_absent_sensor.py)

Two overlapping zero-volts tests merged into one (all assertions preserved); the OSError-propagation tests already lived in the same file, so nothing was folded in and coverage did not shrink.

7. Literal CSV header assertions (test_logging_core.py)

The two header tests self-compared against the COLUMNS constant (a wrong constant passed). They now assert the literal expected header string written out explicitly.

8. Truncated-header recovery (logging_core.py)

Chosen behavior: refuse loudly. A crash mid-header-write left a partial first line; the writer now validates the header line on open and raises ValueError instead of appending rows under a garbage column list (lock is released; file untouched).

  • Test fails without the fix.

9. ChargeIntegrator docstring

No longer claims RSHUNT is an "unverified LeoRover default": 0.010 Ω was verified off the board 2026-08-07 (PR #187), the shunt measures the buck/5 V logic rail ONLY (motors/servos/IO bypass R21), so mAh/Wh are logic-rail truth and capacity runs are valid only with every bypassed branch idle.

Test counts

  • Baseline on main: 66 passed, 5 skipped
  • This branch: 90 passed, 5 skipped (net +24: +4 non-finite config, +3 non-finite voltage, +13 OCV knots/sort, +1 legacy NaN pin, +3 charging edges, +1 truncated header, −1 merged absent-sensor dupe)

CodeAnt-AI Description

Reject invalid power measurements and protect battery logs from corrupted data

What Changed

  • Non-finite voltage readings now produce an unknown SOC instead of falsely reporting a full or empty battery.
  • Battery status distinguishes full, idle, discharging, and invalid-current readings; invalid current never claims the pack is charging.
  • The CSV logger rejects non-finite rotation and sync settings and refuses to append to files with truncated or corrupted headers.
  • Tests now pin the complete SOC curve, literal CSV headers, absent-sensor behavior, and charging-status edge cases.
  • Documentation clarifies that charge and energy totals represent the monitored logic rail, not the entire pack.

Impact

✅ No fabricated battery percentages from invalid voltage
✅ Clearer full and idle charging status
✅ Fewer corrupted or unparseable power logs

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

AI Assistant added 3 commits August 7, 2026 20:15
… in DurableCsvWriter

NaN/inf max_bytes and fsync_every_n slip past a bare `<= 0` check (both
comparisons are False): rotation never fires (silent disk fill) and fsync
never runs (zero durability). Refuse loudly instead, like the INA219
threshold validation. A truncated/corrupt header on open (crash mid-header-
write) previously appended rows under a garbage column list; now refuse to
start rather than corrupt every downstream parse.

Also updates the stale ChargeIntegrator docstring: RSHUNT 0.010 ohm was
verified off the board 2026-08-07 (PR #187) and measures the buck/5 V logic
rail only, so mAh/Wh are logic-rail truth and capacity runs are valid only
with every bypassed branch idle.

Tests: NaN/inf rejection cases for both parameters, refuse-on-corrupt-header
(including lock release), and literal header assertions replacing the
COLUMNS self-comparison.
…voltage_to_soc

+inf previously clamped to 1.0 ('100 % battery') and -inf to 0.0 on garbage
input, because only NaN was guarded. Any non-finite volts is a failed
measurement, so it now stays NaN -- never a fabricated SOC.

Tests: non-finite guard (inf/-inf/nan), full 12-knot OCV parametrization
with count and strict-ascending assertions, and a pin that
legacy_fake_percentage(NaN) stays NaN.
…sor test

- is_charging with NaN current must not claim charging.
- build_telemetry status branches FULL and NOT_CHARGING now asserted
  exactly (CHARGING/DISCHARGING already were).
- the duplicated zero-volts absent-sensor tests merge into one; the
  OSError-propagation tests already lived in the same file, so behavior
  coverage is unchanged.
Copilot AI lite review requested due to automatic review settings August 8, 2026 01:20
@codeant-ai

codeant-ai Bot commented Aug 8, 2026

Copy link
Copy Markdown

🤖 CodeAnt AI — Review Status

Status Commit Started (UTC) Finished (UTC)
✅ Reviewed your PR c4a4e7c Aug 08, 2026 · 01:20 01:26

@codeant-ai

codeant-ai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@codeant-ai codeant-ai Bot added the size:L This PR changes 100-499 lines, ignoring generated files label Aug 8, 2026
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Fix beast_power edge cases: reject non-finite configs, guard SoC, harden CSV headers

🐞 Bug fix 🧪 Tests 📝 Documentation 🕐 20-40 Minutes

Grey Divider

AI Description

• Reject NaN/inf DurableCsvWriter settings and refuse corrupt/truncated CSV headers.
• Treat non-finite pack voltage as invalid (NaN) instead of clamping to 0/100%.
• Add literal, table-pinning tests to prevent silent behavior regressions.
Diagram

graph TD
A["logging_core.py: DurableCsvWriter"] --> B[("CSV log file")] --> C("test_logging_core.py")
D["soc.py: voltage_to_soc"] --> E("test_soc_curve.py")
F["telemetry.py: build_telemetry/is_charging"] --> G("test_sign_convention.py") & H("test_absent_sensor.py")
subgraph Legend
direction LR
_mod["Module"] ~~~ _file[("File on disk")] ~~~ _test("Test")
end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Auto-repair/overwrite header on mismatch
  • ➕ Allows logger to continue after crash mid-header-write without operator intervention
  • ➕ Can self-heal common corruption modes
  • ➖ Risk of silently masking real schema drift (changed columns) and producing mixed-format files
  • ➖ Harder to guarantee downstream parsers won’t misinterpret partially-written data
2. Atomic header creation (write temp then rename)
  • ➕ Eliminates most truncated-header scenarios by making header write all-or-nothing
  • ➕ Still allows strict refusal on genuine schema mismatch
  • ➖ More filesystem complexity (temp naming, atomic rename semantics across platforms)
  • ➖ Doesn’t address non-finite config validation (still needed)
3. Versioned header/schema marker (e.g., first line includes schema id)
  • ➕ Explicitly supports controlled schema evolution and compatibility checks
  • ➕ Clearer errors for downstream tooling
  • ➖ Requires coordinated updates to log parsers and any existing tooling
  • ➖ More intrusive change than needed for the current safety holes

Recommendation: The PR’s chosen strategy (strict validation + loud refusal) is appropriate for safety-critical telemetry logging: it prevents silent disk-fill/zero-durability and avoids corrupting every downstream parse by appending under a bad header. If truncated headers are expected to occur operationally, consider a follow-up that makes header creation atomic (temp + rename) while keeping the current strict header verification on open.

Files changed (6) +172 / -19

Bug fix (2) +41 / -9
logging_core.pyHarden DurableCsvWriter config validation and refuse corrupt headers +37/-8

Harden DurableCsvWriter config validation and refuse corrupt headers

• Rejects non-finite (NaN/inf) max_bytes and fsync_every_n values that previously bypassed naive comparisons and could disable rotation/fsync. Adds a startup header integrity check that raises ValueError if the existing file’s first line does not exactly match expected columns. Updates ChargeIntegrator docstring to reflect verified shunt value and measurement scope.

robot/beast/ros2_ws/src/ugv_main/beast_power/beast_power/logging_core.py

soc.pyReturn NaN for any non-finite voltage in voltage_to_soc +4/-1

Return NaN for any non-finite voltage in voltage_to_soc

• Expands the input guard from NaN-only to all non-finite values using math.isfinite(). Prevents +inf/-inf from being clamped to 1.0/0.0 and instead treats them as invalid measurements (NaN).

robot/beast/ros2_ws/src/ugv_main/beast_power/beast_power/soc.py

Tests (4) +131 / -10
test_absent_sensor.pyConsolidate absent-sensor 0V assertions and clarify gating expectations +4/-8

Consolidate absent-sensor 0V assertions and clarify gating expectations

• Strengthens the absent-sensor test to ensure present=False with 0.0V yields UNKNOWN/NaN semantics and cannot be misread as 0% SOC. Removes a redundant overlapping test while preserving the assertions via the expanded primary test.

robot/beast/ros2_ws/src/ugv_main/beast_power/test/test_absent_sensor.py

test_logging_core.pyAdd literal header assertions and new failure-mode tests for DurableCsvWriter +33/-2

Add literal header assertions and new failure-mode tests for DurableCsvWriter

• Pins the on-disk CSV header as a literal string to avoid self-validating against a potentially wrong COLUMNS constant. Adds tests for refusing to append under a truncated/corrupt header (including lock release behavior) and parametrized rejection of NaN/inf max_bytes and fsync_every_n.

robot/beast/ros2_ws/src/ugv_main/beast_power/test/test_logging_core.py

test_sign_convention.pyExpand charging/status edge coverage (NaN current, FULL/NOT_CHARGING) +44/-0

Expand charging/status edge coverage (NaN current, FULL/NOT_CHARGING)

• Adds a regression test ensuring NaN current never claims charging. Extends telemetry status coverage to assert FULL when at/above full SoC while charging, and NOT_CHARGING for idle-deadband and NaN-current scenarios.

robot/beast/ros2_ws/src/ugv_main/beast_power/test/test_sign_convention.py

test_soc_curve.pyPin OCV knot table behavior and non-finite voltage semantics +50/-0

Pin OCV knot table behavior and non-finite voltage semantics

• Adds tests that pin legacy_fake_percentage(NaN) behavior and ensure voltage_to_soc returns NaN for inf/-inf/NaN. Introduces a literal 12-knot OCV table in the test suite to verify exact knot mapping, strict ascending order, and fixed knot count to catch silent table edits.

robot/beast/ros2_ws/src/ugv_main/beast_power/test/test_soc_curve.py

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens beast_power against non-finite/invalid telemetry and logger configuration values, and expands the unit test suite to prevent regressions that could silently corrupt battery status reporting or CSV logs.

Changes:

  • Reject non-finite voltages in voltage_to_soc() (return NaN instead of clamping to 0/1) and add tests that pin the full OCV knot table and legacy NaN behavior.
  • Strengthen DurableCsvWriter to reject non-finite config values and refuse to append to CSVs with truncated/corrupt headers; update tests to assert literal headers and truncated-header refusal.
  • Add charging-status edge coverage (NaN current, FULL vs NOT_CHARGING branches) and consolidate overlapping absent-sensor assertions.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
robot/beast/ros2_ws/src/ugv_main/beast_power/beast_power/logging_core.py Adds finite checks for writer config and validates header integrity before appending; clarifies ChargeIntegrator docstring.
robot/beast/ros2_ws/src/ugv_main/beast_power/beast_power/soc.py Guards SOC computation against any non-finite voltage input by returning NaN.
robot/beast/ros2_ws/src/ugv_main/beast_power/test/test_logging_core.py Pins literal CSV headers, adds truncated-header refusal test, and adds NaN/inf config rejection cases.
robot/beast/ros2_ws/src/ugv_main/beast_power/test/test_soc_curve.py Adds non-finite SOC tests, pins legacy NaN behavior, and pins the full OCV curve (values/order/count).
robot/beast/ros2_ws/src/ugv_main/beast_power/test/test_sign_convention.py Adds tests for NaN current handling and explicitly asserts FULL / NOT_CHARGING status branches.
robot/beast/ros2_ws/src/ugv_main/beast_power/test/test_absent_sensor.py Consolidates the absent-sensor “never treat 0V as 0% SOC” assertions and adds clarifying doc/comments.
Suppressed comments (1)

robot/beast/ros2_ws/src/ugv_main/beast_power/beast_power/logging_core.py:224

  • fsync_every_n is typed as int, but the current math.isfinite(...) validation still allows floats (e.g. 1.5) and booleans (True -> 1). Since this value controls a write counter, it’s safer to require an integer >= 1 and reject bool to avoid silent misconfiguration.
        if not math.isfinite(fsync_every_n) or fsync_every_n < 1:
            raise ValueError('fsync_every_n must be >= 1 and finite')

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +215 to +220
# NaN/inf slip past a naive `<= 0` check (both comparisons are False)
# and would disable rotation entirely: `tell() >= NaN` is never True,
# so an unattended logger fills the disk instead of rotating. Same
# rejection rule the INA219 threshold validation uses.
if not math.isfinite(max_bytes) or max_bytes <= 0:
raise ValueError('max_bytes must be positive and finite')
@qodo-code-review

qodo-code-review Bot commented Aug 8, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Append now requires read ✓ Resolved 🐞 Bug ☼ Reliability
Description
DurableCsvWriter now reads the first line of an existing non-empty log file to validate the header
before opening in append mode, so a process with append/write-but-no-read permission will fail to
start. This can break existing “append-only” log permission setups and crash the logger node at
initialization.
Code

robot/beast/ros2_ws/src/ugv_main/beast_power/beast_power/logging_core.py[R312-315]

+        with open(self._path, 'r', encoding='utf-8', newline='') as probe:
+            first_line = probe.readline()
+        expected = ','.join(self._columns)
+        if first_line.rstrip('\r\n') != expected:
Relevance

●● Moderate

Team favors “fail loudly” durability checks, but permission/backward-compat tradeoffs are
ops-sensitive; no close precedent on append-only perms.

PR-#184
PR-#171

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
_open() performs a header validation path for any existing non-empty file, and _check_header()
reads the file via open(..., 'r'), which requires read permission even if appending would
otherwise work. The ROS logger node constructs DurableCsvWriter during initialization without
catching PermissionError/ValueError, so this will prevent the node from starting in such
deployments.

robot/beast/ros2_ws/src/ugv_main/beast_power/beast_power/logging_core.py[292-320]
robot/beast/ros2_ws/src/ugv_main/beast_power/beast_power/logger_node.py[85-92]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`DurableCsvWriter._open()` now calls `_check_header()` for existing non-empty files, and `_check_header()` opens the log with `'r'`. This introduces a new requirement: the process must have read permission on the existing CSV. In environments configured for append-only logging, startup will now fail (PermissionError), preventing `beast_power_logger` from running.

### Issue Context
The header integrity check is valuable, but the failure mode should be made explicit and/or configurable so operators aren’t surprised by a permission-related crash when rotating/restarting.

### Fix Focus Areas
- robot/beast/ros2_ws/src/ugv_main/beast_power/beast_power/logging_core.py[292-320]

### Suggested implementation direction
- Catch `PermissionError` (and optionally other `OSError`s) in `_check_header()` and re-raise a clearer `ValueError` explaining that reopening an existing log requires read access (or advise deleting/chowning the file).
- Alternatively/additionally, introduce a constructor flag (e.g., `validate_header: bool = True`) so environments that *must* be append-only can opt out explicitly (with clear documentation of the corruption risk).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context used
✅ Compliance rules (platform): 17 rules
✅ Skills: hangar-logbook

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment thread robot/beast/ros2_ws/src/ugv_main/beast_power/beast_power/logging_core.py Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c4a4e7c6d2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

with open(self._path, 'r', encoding='utf-8', newline='') as probe:
first_line = probe.readline()
expected = ','.join(self._columns)
if first_line.rstrip('\r\n') != expected:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reject headers missing their record terminator

When a power loss persists every header character but not the trailing newline, readline() returns the expected text and this comparison accepts it because stripping line endings cannot distinguish a missing terminator. The writer then opens in append mode and glues the first telemetry row directly to note, corrupting the CSV header and the experiment log; require an actual \n/\r\n terminator (or repair/rotate the file) before appending.

Useful? React with 👍 / 👎.

Comment on lines +312 to +315
with open(self._path, 'r', encoding='utf-8', newline='') as probe:
first_line = probe.readline()
expected = ','.join(self._columns)
if first_line.rstrip('\r\n') != expected:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The header check strips line endings before comparing, so a file containing the complete header without its terminating newline is accepted. Appending then writes the first data row directly after the final note header text, producing a corrupt first CSV record—the exact crash-mid-header case this check is intended to prevent. Require the probed line to end in \n or \r\n before accepting it. [logic error]

Severity Level: Major ⚠️
- ❌ First telemetry row merges with the CSV header.
- ❌ Offline power-log parsing becomes malformed.
- ⚠️ Logger startup accepts corrupted persisted state.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** robot/beast/ros2_ws/src/ugv_main/beast_power/beast_power/logging_core.py
**Line:** 312:315
**Comment:**
	*Logic Error: The header check strips line endings before comparing, so a file containing the complete header without its terminating newline is accepted. Appending then writes the first data row directly after the final `note` header text, producing a corrupt first CSV record—the exact crash-mid-header case this check is intended to prevent. Require the probed line to end in `\n` or `\r\n` before accepting it.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

…ct bool/non-integral config

Bot review follow-up on PR #190 (3 findings):

1. Header missing its record terminator was accepted. A power loss can
   persist every header character but not the trailing newline; an 'a'
   append then merged the first data row into the header text. The probe
   now requires the first line to end with '\n' (covers \r\n too); a
   bare unterminated header raises ValueError like any other corrupt one.

2. The header probe used a separate open(path, 'r'), so a writer holding
   only write/append permission on its log file failed at startup. _open
   now opens a single 'a+' handle: appends always land at EOF, the header
   probe is an explicit read on the same handle (seek(0), then seek to
   END). No second open, no extra permission requirement; header write on
   an empty file, fsync, and rotation behavior are unchanged. The handle
   is closed (not leaked) if the probe refuses.

3. max_bytes/fsync_every_n accepted bools and non-integral floats. Bools
   are ints to Python and silently mean 1; 1.5 would make rotation/fsync
   fire at surprising times. Both rejected. Integral floats (YAML '5e6')
   stay accepted.

Tests: unterminated-header refusal (with lock-release check), 4 new
invalid-config cases (bool/non-integral for each parameter), and an
integral-float acceptance test. All 5 new tests failed before the fix.
Full suite: 96 passed, 5 skipped (was 90).
@Coldaine

Coldaine commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator Author

Bot-review follow-up — all three findings fixed in 5121f04

Full suite: 96 passed, 5 skipped (was 90). CI-equivalent invocation (python -m pytest src/ugv_main/beast_power/test -q, no PYTHONPATH) verified locally on Windows; all new tests failed before the fix.

1. Codex P1 + CodeAnt — unterminated header accepted → fixed in 5121f04

A power loss can persist every header byte but not the trailing newline; the old rstrip('\r\n') != expected check then passed and an 'a' append merged the first data row into the header text. The probe now requires the first line to actually end with '\n' (covers '\r\n' too); a bare terminator-less header raises ValueError like any other corrupt one.

  • Regression test test_terminatorless_header_refuses_to_append: writes the exact header bytes with NO trailing newline, asserts ValueError, file untouched, lock released (second attempt raises the same refusal, not LogAlreadyActive). Failed before the fix.

2. Qodo — "append now requires read" → fixed in 5121f04 (chosen fix: 'a+' restructure)

The probe was a separate open(path, 'r'), so a writer holding only write/append permission on its log file failed at startup. _open now opens a single 'a+' handle: appends always land at EOF (mode semantics), and the header probe is an explicit read on the same handle — seek(0)readline()seek(0, SEEK_END). No second open, no extra permission requirement, no TOCTOU between probe and append open. Behavior is otherwise identical: header written on empty/new file, fsync after header write, rotation unchanged. The handle is closed (not leaked) if the probe refuses, so __init__'s lock release still leaves no fd behind.

  • Rationale vs the alternative (catch PermissionError on the probe and refuse loudly): 'a+' is cleaner — one open, no probe-failure path to special-case — and the logger always holds rw on the file it created on the robot. The write-only-permission case is not supported by this package's runtime.

3. Copilot — bools and non-integral numerics accepted → fixed in 5121f04 (disposition: partial)

max_bytes=True (bools are ints to Python, silently meaning 1) and max_bytes=1.5 (rotation/fsync would fire at surprising times) are now rejected with ValueError; same for fsync_every_n. Integral floats stay accepted per the disposition — YAML configs legitimately produce 5e6 — and test_integral_float_config_accepted pins that rotation + fsync still fire with max_bytes=400.0 / fsync_every_n=2.0.

  • One rejection test case each (max_bytes bool, max_bytes non-integral, fsync_every_n bool, fsync_every_n non-integral), all failed before the fix.

Head: 5121f04 (was c4a4e7c). Diff vs previous head: logging_core.py + test_logging_core.py only.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants