Skip to content

SOW-0156: systemd v260.1 header rules and 4 GiB ceiling in both SDKs - #18

Open
ktsaou wants to merge 25 commits into
sow-0137/array-openfrom
sow-0156/header-audit
Open

ktsaou wants to merge 25 commits into
sow-0137/array-openfrom
sow-0156/header-audit

Conversation

@ktsaou

@ktsaou ktsaou commented Nov 1, 2026 •

Copy link
Copy Markdown
Member

Stacked on #17. Merge order: #13, #14, #15, #16, #17, then this. SOW-0156 (.agents/sow/done/SOW-0156-20261010-fixed-header-consistency-audit.md) is completed.

Decisions for review

  • Agent-chosen, pending your review:
    • Error names: Go ErrTimestampOutOfRange / ErrJournalFileFull, Rust JournalError::TimestampOutOfRange / JournalError::JournalFileFull (both Rust variants are unreleased and listed in docs/Rust-API.md).
    • A1, A4, A5 (Rust header gates at Go parity), approved 2026-11-03.
    • The effective file ceiling is 2^32 - 8 MiB, not 4 GiB: the arena grows in 8 MiB steps, so the last object must end there for the file to stay at or below 2^32 - 1.
  • Decided by you (2026-11-03): D1 = A, D2 = B, D3 = A (old large files stay supported), D4 = A, D5 = A, D6 = A, N1 = A (continuation is SOW-0171), N2 = A.
  • New, needs a decision later (not blocking): SOW-0172. The Rust Log accepts a relative directory, then fails on the first write; Go works. Options and a recommendation are in the SOW.

What changes

  • Append-open in both SDKs refuses, before mutation, what systemd v260.1 will not write: header size other than 272, unknown compatible flags, a missing tail-entry-boot-ID flag, archived or sealed files, invalid tail metadata, and files over 4 GiB. Log sets such an active file aside; root-retention Logs archive it.
  • Timestamps: writers refuse realtime outside 1..2^55-1 and monotonic at or above 2^55.
  • 4 GiB ceiling (chunk 2): both writers bound each entry's growth from payload sizes and return the file-full error when the entry could pass the ceiling. Nothing is written and the writer stays usable. Entry arrays stop doubling near the ceiling. Log rotates once and retries.
  • Readers: refuse unknown header states and short sealed headers. Rust accepts single-bucket hash tables. Both ignore the truncated 32-bit tail array hint above 4 GiB, so files earlier releases wrote stay readable.

Validation

  • go test ./... and cargo test --workspace pass, on this branch and on the top of the stack (SOW-0153: Go stability guide links to the install guide; Rust writer-state APIs named #22).
  • Byte identity, compression, compact, live, verify and root-retention parity matrices pass. The journalctl query matrix passes against systemd v262.
  • Docs examples: 35/35 pass.
  • Fixtures written by the v0.10.0 writer (regular, compact, compressed, sealed, out-of-range timestamps, archived-at-active-name, 9.9 GB) are read by both SDKs and continued by Log.
  • Real use: a Log with no limits, fed 1 MiB entries.
    • Go: 22 files and 87,457 rows. Every full file is exactly 2^32 - 8 MiB.
    • Rust: rotates at the same point.
    • Seqnums are continuous in both runs. Go and Rust read each other's files, and both verifiers pass on all 24 files.
  • Benchmarks: interleaved pairs on a loaded host. Wall-clock results are inconclusive; paired user-CPU medians are within noise (Go -1.0% to +4.3%, Rust -8.1% to +3.9%). File size, mmap remap and map counts are identical in every pair.

Summary by cubic

Applies systemd v260.1's writable-open rules and timestamp bounds to both SDKs, bringing Rust header gates to Go parity, and records the SOW-0156 decisions that unblocked the work.

  • Both SDKs' append-open refuses header sizes other than 272, unknown compatible flags, a missing tail-entry-boot-ID flag, archived files, sealed files, invalid tail metadata, and regular files over 4 GiB, before any mutation. Log sets such an active file aside and continues the sequence; root-retention logs archive it under its archive name instead of failing to start. Continuing sealed files with the sealing key is tracked in SOW-0171.
  • Both SDKs' writers refuse realtime outside 1..2^55-1 and monotonic at or above 2^55 before mutation via new ErrTimestampOutOfRange / TimestampOutOfRange errors; Log no longer inherits out-of-range tails as clock floors.
  • Both SDKs' writers now refuse an entry that could grow the file past 4 GiB via new ErrJournalFileFull / JournalError::JournalFileFull errors, rotating once and retrying in directory writers, and returning the entry when it does not fit an empty file.
  • Both SDKs' opens reject unknown header states and short sealed headers before mutation; Log now sets such active files aside at startup and keeps the sequence instead of stopping, with Go's chain scan reading those headers for sequence identity. Rust accepts single-bucket hash tables, and both ignore the truncated 32-bit tail array hint above 4 GiB, keeping files from earlier releases readable.
  • Rejected opens and refused creates leave files byte-for-byte unchanged, proven by paired Go/Rust tests: both writers validate a new file's layout and sealing options before opening. Fixtures come from the v0.10.0 writer. The user confirmed the error names and parked the Rust relative-directory finding as SOW-0172.

Written for commit 4ff1aca. Summary will update on new commits.

View guided diff Turn on auto-fix

Rust readers now refuse unknown incompatible flags, and Rust append-open
refuses compact files over 4 GiB and files missing a hash table, as Go
already does. Adds paired Go/Rust tests and records the header audit with
the remaining behavior decisions in SOW-0156.
@coderabbitai

coderabbitai Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 21412457-3596-40d0-9f7d-86421cff9358

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@codacy-production

codacy-production Bot commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 430 complexity · 37 duplication

Metric Results
Complexity 430
Duplication 37

View in Codacy

AI Reviewer: first review requested successfully. AI can make mistakes. Always validate suggestions.

Run reviewer

TIP This summary will be updated as you push new changes.

@codacy-production codacy-production Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull Request Overview

✅ No blocking issues noticed

One acceptance criterion appears only partially covered: the header inventory validation lacks a dedicated test for unknown incompatible flags. The Rust test file is also relatively complex and has clone findings, so adding focused coverage would reduce regression risk.

Status tracking is inconsistent between the root and canonical indexes and should be reconciled, though this does not indicate a runtime defect. No high-confidence blocking issues were found.

Test suggestions

  • Rust reader rejects an unknown incompatible flag.
  • Rust append-open rejects an unknown incompatible flag.
  • Rust append-open rejects a file missing the DATA hash table.
  • Rust append-open rejects a file missing the FIELD hash table.
  • Rust append-open rejects compact files larger than 4 GiB without resizing them.
  • Go reader and append-open paths reject unknown incompatible flags.
  • The header inventory path rejects unknown incompatible flags.
  • Each rejected open leaves the file byte-for-byte unchanged.
Prompt proposal for missing tests
Consider implementing these tests if applicable:
1. The header inventory path rejects unknown incompatible flags.
Low confidence findings
  • Reconcile the root and canonical status indexes so they present the same current-work state.

TIP Improve review quality by adding custom instructions
TIP How was this review? Give us feedback

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 10 files

Architecture diagram
sequenceDiagram
    participant Client as SDK Client
    participant Reader as Rust Reader (file.rs)
    participant Writer as Rust Append-Writer (file_mut.rs)
    participant Header as JournalHeader Validator
    participant Inv as header_inventory.rs
    participant Go as Go SDK (reference)
    participant Sys as systemd (reference)

    Note over Client,Sys: Rust header open gates at Go parity

    Client->>Reader: open(path)
    Reader->>Header: validate header
    Header->>Header: check incompatible_flags & !SUPPORTED_INCOMPATIBLE_FLAGS
    alt Unknown incompatible flags present (A1)
        Header-->>Reader: UnsupportedJournalFile
        Reader-->>Client: error, file unchanged
    else Flags OK
        Reader->>Reader: continue normal open
    end

    Client->>Writer: open_for_append(path)
    Writer->>Header: validate header
    Header->>Header: check has KeyedHash flag + all flags supported
    alt Missing KeyedHash or unsupported flags (A1)
        Header-->>Writer: UnsupportedJournalFile
        Writer-->>Client: error, file unchanged
    else Flags OK
        Header->>Header: check data_hash_table_offset, field_hash_table_offset, tail_object_offset
        alt Any hash table missing (A5)
            Header-->>Writer: InvalidObjectLocation
            Writer-->>Client: error, file unchanged
        else All tables present
            Header->>Header: check Compact flag + file_size limits
            alt Compact file exceeds 4 GiB (A4)
                Header-->>Writer: ObjectExceedsFileBounds
                Writer-->>Client: error, file unchanged
            else Size OK
                Writer->>Writer: proceed with append operations
                Writer-->>Client: success
            end
        end
    end

    Note over Inv,Header: Shared flag constant
    Header->>Inv: SUPPORTED_INCOMPATIBLE_FLAGS
    Inv->>Inv: validate_writer_compatibility uses same constant

    Note over Go,Sys: Reference implementations
    Go-->>Sys: Go already follows systemd behavior
    Sys-->>Go: for A1, A4, A5
Loading

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread SOW-status.md Resolved

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 4 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread SOW-status.md Resolved

@cubic-dev-ai cubic-dev-ai 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.

2 issues found across 2 files (changes from recent commits).

Confidence score: 4/5

  • SOW-0156-20261010-fixed-header-consistency-audit.md has conflicting lifecycle records: its status is in progress, while the plan, execution log, and outcome still describe D1–D6 as pending or paused. Align those records.
  • SOW-status.md still lists SOW-0156 as paused with D1–D6 pending. Update the index to match the SOW.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name=".agents/sow/SOW-status.md">

<violation number="1" location=".agents/sow/SOW-status.md:35">
P3: The root `SOW-status.md` still reports SOW-0156 as paused with D1-D6 pending. Update that entry in the same change so the two status indexes match.</violation>
</file>

<file name=".agents/sow/current/SOW-0156-20261010-fixed-header-consistency-audit.md">

<violation number="1" location=".agents/sow/current/SOW-0156-20261010-fixed-header-consistency-audit.md:5">
P2: `Status: in-progress` now conflicts with the SOW's remaining lifecycle records: `Plan` says D1-D6 are open, the execution log says it is paused on them, and `Outcome` still says the user is deciding them. Update those records so the status, plan, and outcome describe the same state.</violation>
</file>

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread .agents/sow/SOW-status.md Resolved
Apply systemd v260.1's writable-open header rules in both SDKs: append-open
refuses header sizes other than 272, unknown compatible flags, a missing
tail-entry-boot-ID flag, archived files, invalid tail metadata and regular
files above 4 GiB as unsupported, so Log sets them aside and continues the
sequence. Root retention finalizes retired files through an archive-only open.

Writers refuse realtime outside 1..2^55-1 and monotonic at or above 2^55
before mutation (Go ErrTimestampOutOfRange, Rust TimestampOutOfRange), and Log
no longer inherits out-of-range tails as clock floors.

Readers refuse unknown header states and short sealed headers, accept
single-bucket hash tables in Rust, and ignore the truncated 32-bit tail entry
array hint above 4 GiB, so files earlier releases grew past 4 GiB stay
readable. Validated against fixtures written by the v0.10.0 writer.

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 36 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread go/README.md Resolved
Comment thread go/journal/header_append_target_test.go Resolved
Comment thread .agents/sow/specs/product-scope.md Resolved
Comment thread .agents/sow/specs/product-scope.md Resolved
Comment thread go/journal/format.go Resolved
Append-open now refuses sealed files in both SDKs, because the writer cannot
restore sealing state and would append an unsealed tail; Log sets such a file
aside and starts a new one. Continuing sealing with the key is tracked in
SOW-0171.

Root-retention logs archive an active file they cannot extend under its
archive name instead of failing to start, since root inventory rejects
set-aside files. Go archive-only opens no longer require a boot ID.

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 15 files (changes from recent commits).

Requires human review: Auto-approval blocked because this review re-detected 1 unresolved issue already reported by Cubic.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread go/journal/log.go Resolved
Comment thread go/journal/log.go Resolved
Comment thread docs/Writer-APIs.md Resolved
The header's tail entry array hint and compact offsets are 32-bit, so a
file must never reach 4 GiB. Before an entry mutates the file, both writers
bound its growth from payload sizes (DATA, FIELD and a minimum entry array
per item, the ENTRY, a minimum global array and due TAGs) and refuse it with
Go ErrJournalFileFull / Rust JournalError::JournalFileFull when it could pass
2^32 - 8 MiB. Nothing is written and the writer stays usable. Entry arrays
stop doubling near the ceiling, and arena growth and creation refuse sizes
above 2^32 - 1 for both layouts.

The directory writers rotate once on this error and retry the entry; an
entry that does not fit an empty file is returned, and the Rust Log restores
its clocks as Go does.

Completes SOW-0156 and tracks the Rust relative-directory finding as
SOW-0172.
@ktsaou ktsaou changed the title SOW-0156: Rust header open gates at Go parity; header audit decisions SOW-0156: systemd v260.1 header rules and 4 GiB ceiling in both SDKs Nov 3, 2026

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 22 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread docs/Rust-API.md Resolved
…e create

Rust append and archive-only opens now refuse an unknown state and a short
sealed header, as readers and Go already did. Both writers validate the new
file's layout (and Go its sealing options) before opening, so a refused create
leaves an existing target untouched. Go header errors name the refused value.

Adds paired tests, fixtures from earlier release tags, and corrects the writer
docs and spec for the 4 GiB boundary, invalid files and the append ceiling.

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 18 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread .agents/sow/specs/product-scope.md Resolved
Log stopped at startup on an unknown state or short sealed header, where
journald sets the file aside. Both languages now do the same and keep the
sequence; root retention reports the error. Go's chain scan reads such headers
for sequence identity, matching Rust. Move the writable header refusals into
one helper and note the zero-realtime exception in the spec.

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant