Skip to content
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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
11 changes: 4 additions & 7 deletions .agents/sow/SOW-status.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,9 @@ order; the user may reorder it.
differs (completed 2026-11-06, options A and 2B).
22. SOW-0175 - Split the journalctl rewrites by responsibility (tracked by user
decision 2026-11-07; after SOW-0097).
23. SOW-0176 - Single ownership follow-ups: open decisions 8 and 9, I7 and
divergences found in SOW-0097 (needs user decisions).

## Current

None.

## Pending

- SOW-0066 - V1 Release: v1.0.0 API freeze and release; not scheduled (user
Expand Down Expand Up @@ -77,14 +73,15 @@ None.
by user decision.
- SOW-0171 - Continue Sealing When Reopening A Sealed File: tracked
2026-11-03 (SOW-0156 decision N1); not scheduled.
- SOW-0177 - Report The Validated Sealed Range From Verification: tracked from
Comment thread
ktsaou marked this conversation as resolved.
Resolved
SOW-0176 item I7; needs a public API decision.
- SOW-0175 - Split The journalctl Rewrites By Responsibility: tracked
2026-11-07 (SOW-0097 decision 7); after SOW-0097.
- SOW-0176 - Single Ownership Follow-ups: tracked 2026-11-08 from SOW-0097;
needs user decisions (legacy export contract, Rust list_boots Result) plus
I7 and six Go/Rust or SDK/systemd divergences.

## Recently Closed Or Completed

- SOW-0176 - Single Ownership Follow-ups: completed 2026-11-08; follow-up
SOW-0177.
- SOW-0174 - Rust Registry Unwatch Leaves Files When The Directory Key
Differs: completed 2026-11-06. Unwatch forgets exactly the files no
remaining watch covers, overlapping watches work in any order, failures
Expand Down
404 changes: 404 additions & 0 deletions .agents/sow/done/SOW-0176-20261108-single-ownership-followups.md

Large diffs are not rendered by default.

129 changes: 0 additions & 129 deletions .agents/sow/pending/SOW-0176-20261108-single-ownership-followups.md

This file was deleted.

135 changes: 135 additions & 0 deletions .agents/sow/pending/SOW-0177-20261108-verify-sealed-range-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# SOW-0177 - Report The Validated Sealed Range From Verification

## Status

Status: open

`completed` is the successful terminal status. `done` is a directory name, not a status value. Do not use `Status: done` or `Status: complete`.

Sub-state: tracked from SOW-0176 (item I7) on 2026-11-08; needs a public API decision.

## Requirements

### Purpose

Make `journalctl --verify --verify-key` print the same validated-range summary
as systemd v262 for sealed files.

### User Request

Follow-up of the SOW-0176 verify-key parity decision (I7 = A).

### Assistant Understanding

Facts:

- After `PASS`, v262 prints `=> Validated from %s to %s, final %s entries not
sealed.`, `=> No sealing yet, %s of entries not sealed.` or `=> No sealing
yet, no entries in file.` (systemd/systemd @ 8cc40e0c5e9234bf45084751ac53b1fbfe70b492
`src/journal/journalctl-misc.c:70-85`).
- `VerifyFileWithKey` and `verify_file_with_key` return only success or failure.
Both already track the realtime of the last verified TAG.

Inferences:

- Exposing the range needs a new public result type in both SDKs, plus
`format_timespan` output in the CLIs.

Unknowns:

- The public API shape, decided at activation.

### Acceptance Criteria

- Both CLIs print the three v262 summary forms, compared against the pinned
OpenSSL-enabled v262 oracle.
- The range comes from one public SDK function per language.

## Analysis

Sources checked: SOW-0176 keydiff results and the files cited above.

## Pre-Implementation Gate

Status: blocked

Problem / root-cause model:

- The verifier does not return the range v262 reports.

Evidence reviewed:

- See `## Requirements`.

Affected contracts and surfaces:

- Go and Rust verification APIs, both journalctl rewrites.

Existing patterns to reuse:

- `verifySealed` / `verify_sealed` tag tracking.

Risk and blast radius:

- Additive API; stderr output only.

Sensitive data handling plan:

- Synthetic fixtures only.

Implementation plan:

1. Decide the API, add it in both languages, print the summary, compare with the oracle.

Validation plan:

- `tests/interoperability/run_verify_matrix.py` and a stderr comparison.

Artifact impact plan:

- AGENTS.md: none expected.
- Runtime project skills: none expected.
- Specs: journalctl parity matrix.
- End-user/operator docs: API pages.
- End-user/operator skills: none.
- SOW lifecycle: pending until activated.

Open-source reference evidence:

- Cited above.

Open decisions:

- Public API shape.

## Implications And Decisions

None yet.

## Plan

See the implementation plan above.

## Execution Log
Comment thread
ktsaou marked this conversation as resolved.
Resolved

None yet.

## Validation

Pending.

## Outcome

Pending.

## Lessons Extracted

Pending.

## Followup

None yet.

## Regression Log

None.
2 changes: 1 addition & 1 deletion .agents/sow/specs/journalctl-v260-parity-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ Every official v262 output mode is `file-backed-required`:

| Option | Args | Classification | Required portable behavior |
| --- | --- | --- | --- |
| `--verify-key=` | key | file-backed-required | Verify sealed journal files when combined with `--verify` or implied verification. Invalid seed/key text must fail with official-style parse errors. |
| `--verify-key=` | key | file-backed-required | Verify sealed journal files when combined with `--verify` or implied verification. Key text follows v262 `journal_auth_load_key` through the SDK parser, is judged per file, and a rejected key stops the run with v262's message. |
| `--interval=` | duration | recognized-no-op | Parse for `--setup-keys`; no portable behavior exists without `--setup-keys`, so it is accepted as a no-op elsewhere. |
| `--force` | none | recognized-no-op | Parse for `--setup-keys`; no portable behavior exists without `--setup-keys`, so it is accepted as a no-op elsewhere. |
| `--setup-keys` | none | recognized-unsupported | Official v262 checks `/var/log/journal`, reads host machine and boot IDs, and writes `/var/log/journal/<machine-id>/fss`; portable file-backed commands must reject it instead of mutating host journal state. Evidence: `systemd/systemd @ 8cc40e0c5e9234bf45084751ac53b1fbfe70b492`, `src/journal/journalctl-authenticate.c:80`, `src/journal/journalctl-authenticate.c:89`, `src/journal/journalctl-authenticate.c:93`, `src/journal/journalctl-authenticate.c:97`, `src/journal/journalctl-authenticate.c:139`. |
Expand Down
22 changes: 14 additions & 8 deletions .agents/sow/specs/product-scope.md
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,8 @@ Root retention counts file lengths including preallocation, expires by tail
saved time, and evicts whole files in tail/path order. Default retention policies
and file format are unchanged; Go now applies cleanup after a successful
successor-creation retry following a safe creation failure.
Ordinary `Log` retention in both languages counts disposed `*.journal~` files and
deletes them before archived history, as journald does.
`MaintainRootRetention` finalizes verified retired active
files and only seals live actives for idle expiry or required size-policy
allocation changes, leaving successors lazy even after eager startup. Startup,
Expand Down Expand Up @@ -641,6 +643,10 @@ Current shared high-level directory writer API slice:
leaves the file in place. Default chain naming treats only online files as
active, so an unknown-state chain file stays behind as history while the new
file still continues its sequence.
- Rust and Go non-root directory retention counts disposed `*.journal~` files
of the same source, both `<source>@...` forms, and deletes them oldest first
and before archives, as journald's vacuum does. Max-age retention deletes any
expired file whatever its place in that order.
- Rust and Go writers refuse entry timestamps with realtime outside
`1..2^55-1` or monotonic at or above 2^55 before mutation (Go
`ErrTimestampOutOfRange`, Rust `JournalError::TimestampOutOfRange`); the
Expand Down Expand Up @@ -1079,11 +1085,12 @@ Current Go reader feature slice:
- boot listing (`Reader.ListBoots`, `DirectoryReader.ListBoots`,
`SdJournalListBoots`) reports every boot from the `_BOOT_ID` DATA entry
arrays, merged across files in directory order and ordered by last entry,
first entry and ID. When no usable canonical index exists it scans entry
headers instead, so header IDs can name boots then; a directory scans only
when no file has such an index or one cannot be read. Only canonical
lowercase `_BOOT_ID` values name indexed boots. It does not change matches
or position. Both journalctl rewrites use it;
first entry and ID. A journal with no `_BOOT_ID` DATA lists no boots, as
v262 journalctl does; `HeaderFallback` (Rust `header_fallback`) scans entry
headers then, and for a directory only when no file has `_BOOT_ID` DATA.
Noncanonical `_BOOT_ID` DATA counts as present but names no boot. Read
failures return an error. It does not change matches or position. Both
journalctl rewrites use the default;
- file-backed journalctl output modes cover the stock v262 family:
`short`, `short-full`, `short-iso`, `short-iso-precise`,
`short-precise`, `short-monotonic`, `short-delta`, `short-unix`,
Expand Down Expand Up @@ -1229,9 +1236,8 @@ Current Rust reader feature slice:
contract as the Go `EntryEncoder`; both journalctl rewrites render through
it. `export_entry_bytes` and `json_entry` keep the older lossless contract;
- boot listing (`FileReader::list_boots`, `DirectoryReader::list_boots`,
`SdJournalListBoots`) follows the Go boot-listing contract;
`DirectoryReader::list_boots` and `SdJournal::list_boots` cannot return an
error and fall back to one boot per file header when files cannot be read;
`SdJournalListBoots`) follows the Go boot-listing contract, including
returning an error when files cannot be read;
- file-backed journalctl output modes cover the stock v262 family:
`short`, `short-full`, `short-iso`, `short-iso-precise`,
`short-precise`, `short-monotonic`, `short-delta`, `short-unix`,
Expand Down
10 changes: 7 additions & 3 deletions docs/Go-API.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,9 +232,11 @@ formats. `ExportEntry` and `JSONEntry` keep their older, lossless contract.
## List Boots

`ListBoots` lists every boot as `journalctl --list-boots` does, from the
`_BOOT_ID` indexes when a usable canonical index exists, otherwise from entry
headers (a directory scans headers only when no file has such an index or one
cannot be read), without changing matches or the read position.
`_BOOT_ID` indexes, without changing matches or the read position. A journal
with no `_BOOT_ID` DATA lists no boots. Pass
`BootListOptions{HeaderFallback: true}` to `ListBootsWithOptions` to list the
boot IDs from entry headers instead; a directory does so only when no file has
`_BOOT_ID` DATA.

<!-- verify-example: lang=go id=go-list-boots -->
```go
Expand Down Expand Up @@ -759,6 +761,8 @@ if err := journal.VerifyFile("/var/log/journal/example/system.journal"); err !=
}
```

`ParseVerificationKey` validates a key with the same grammar as systemd v262.

Use `VerifyFileWithKey` for sealed files when a verification key is available.
Verification is for integrity checks, not normal query serving.
File-path verification uses the same bounded reader access architecture as
Expand Down
5 changes: 5 additions & 0 deletions docs/Reader-APIs.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,11 @@ Detection is lazy where the native access path permits it. A zero/`None` status
means no loss has been observed; it does not certify integrity. A catalog query
does not validate unrelated entry arrays.

A single unreadable DATA object, or one without a field separator, costs that
field only: the entry is still returned, and a payload without `=` stays in the
raw payload list. A DATA object that cannot be decompressed still fails the
entry.
Comment thread
ktsaou marked this conversation as resolved.
Resolved

Recovery is read-only: the SDK does not reconstruct or overwrite damaged source
journals. Journald abandons a corrupt file and writes to a new one. Normal live
publication can make temporarily unavailable data readable; this is distinct
Expand Down
Loading
Loading