logs.gokuls.in

4 pull requests merged across 2 repos

Offline-Protocol/offline-protocol-sdk

Cuts v0.25.0. Nothing here changes behaviour: it is the version bump, the

release notes, and three corrections found while auditing the range.

Minor rather than patch, decided on public surface rather than on the

commit types. offline-protocol-transport loses ReticulumConfig, both its

constructors-adjacent helpers, ReticulumTransportBuilder and two constants,

and the TypeScript PresenceSource and SecurityWarningCode unions widen by

one and two members. The .udl and types.ts diffs were checked with comment

lines stripped rather than argued from semver intuition.

The release itself is four things: Reticulum attaches over

contract v1 so a device on it can receive for

the first time, the inert Reticulum configuration is deleted, the wire

specification becomes implementable from its own text and gains seven

conformance vector files, and two iOS BLE defects that cost battery and blocked

discovery are fixed.

Related issues

Refs #420, #421, #422, #427, #429, #430.

Type of change

  • chore — build, tooling, or dependency changes

Checklist

  • cargo fmt --all -- --check passes
  • cargo clippy --workspace --locked -- -D warnings passes
  • cargo test --workspace passes
  • cargo-deny is satisfied (advisories ok, bans ok, licenses ok, sources ok)
  • Commits follow Conventional Commits
  • Docs / CHANGELOG.md updated where relevant
  • No new unsafe in core crates
  • UDL unchanged in this PR, so no binding regeneration; the generated

bindings embed no version string, which is why a bump does not touch them

Also run: RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps, the

three no_std targets (core, sealed, leaf on thumbv8m.main-none-eabihf,

zero diagnostics each), scripts/check-license-consistency.sh, and

npx tsc --noEmit in bindings/react-native.

Version files

[workspace.package].version and the ten internal dependencies in

[workspace.dependencies]; the four more declared locally in

offline-protocol-sealed and offline-protocol-leaf, where inheritance is

deliberately not used (ADR 0020 / 0022) and

local_dep_versions_match_the_workspace_table is the guard; Cargo.lock plus

the two tool-workspace lockfiles CI builds with --locked;

bindings/python/pyproject.toml; the npm package and its lockfile; and the

three THIRD-PARTY-NOTICES.md copies, which list our own crates by version and

so go stale on every bump. All eleven crates are published at 0.24.1 on the

sparse index and 0.25.0 is free.

Three corrections that are not version bumps

1. A wider reading of the stale-figure class. The wire-spec entry said the

signing-domain registry now lists five live domains and that six vector

files pin the spec. #427 landed later in the same range, added

offline-gateway-addr-v1 and a seventh vector file, and made both wrong.

Neither number reads as a measurement, so the usual "re-measure quoted figures"

pass does not catch it. The rule that does: any cardinal describing a registry,

table or file set that another PR in the same range also adds to. Both entries

are corrected, and the vector-coverage sentence now names the gateway proof.

**2. docs/changelog/0.22.md has shipped eight broken links since its own

archive.** Archiving moves a section two directories down, so ](docs/…) has to

become ](../…); that rewrite was missed at the 0.22.0 cut and nothing in CI

resolves a markdown link, so it survived three releases. Fixed, and

CONTRIBUTING.md's archive paragraph now says to validate the whole tree at

each cut and why "silently" is literal.

3. Cargo.toml said "all nine move together". The internal-dependency table

has been ten since 0.24.0.

Breaking changes

For Rust crate consumers only: ReticulumConfig and its four fields,

ReticulumTransport::with_config / config() / should_reconnect /

increment_reconnect_attempts, ReticulumTransportBuilder, and

RETICULUM_CONNECTION_TIMEOUT_SECS / RETICULUM_MAX_PAYLOAD_SIZE are gone.

ReticulumTransport::new is the one constructor left, and there is nothing to

carry across because nothing read those values. No binding surface,

configuration key or wire byte follows them out; the React Native

transports.reticulum section is untouched.

TypeScript applications that exhaustively switch on PresenceSource or

SecurityWarningCode get a compile error from the widened unions.

Migration is §19, which now carries this release's label.

Notes for reviewers

The changelog diff is large because the section was rebuilt, not appended to.

Two things worth checking rather than skimming:

  • The release header's direction. Reticulum is off by default

(reticulum_enabled: false in TransportConfig::default()), so the "a device

could never receive anything" headline is scoped to deployments that enabled

it and pointed at a daemon. The header says so. It also states the reverse

case, which is the one that can surprise an operator: a daemon speaking the

pre-contract shape now yields a transport that connects and never becomes

available, because the attach times out after ten seconds

(GatewayAttachPolicy.ATTACH_TIMEOUT). This direction-checking is deliberate

after v0.24.1, where the first draft of the release note said the opposite of

what the engine default made true.

  • The archive move is provably lossless. The 0.24.1 section was re-extracted

from git show HEAD:CHANGELOG.md, the link rewrite applied, and the result

diffed against what landed in docs/changelog/0.24.md: identical except the

single ./docs/UPGRADING.md → ../UPGRADING.md line.

[Unreleased] had also accumulated three ### Fixed and three ### Added

headings from per-PR appends. All ten entries are preserved, regrouped into one

heading each under Added / Changed / Removed / Fixed / Documentation, matching

0.23.0 and 0.24.0. The three (#NNN) references are dropped for the same

reason: 0.20.0 had fifteen, and the four releases since carry one between them.

Not done here: adding a markdown link checker to CI. It is out of scope for

a release cut, but correction 2 above went unnoticed for three releases, so it

is a genuine gap worth a follow-up.

After merge

Tag v0.25.0 on main; pushing the tag is what publishes. release.yml's

version-gate runs ahead of both publish jobs and will refuse a tag whose

number disagrees with [workspace.package].version or pyproject.toml, which

is the one point in the release where re-tagging is still safe.

bahdotsh/wrkflw

TODO item 4 (P3, refactor) from the follow-up list off the PR #116 review. Fourth and last item in that series, after #119, #127 and #128.

What the item asked for, and why this PR does something else

The item was written against a pre-#119 tree. It asked to delete the ad-hoc trim_logs_to_cap() calls in check_diff_filter_results, rewrite the stale comment block above them, and route ~7 hand-rolled Local::now().format("%H:%M:%S") sites through add_timestamped_log.

Auditing that against current main:

  • The trim calls and the stale comment are already gone. #119 removed all six ad-hoc calls and the "Ad-hoc self.logs.push(...)" comment. The single surviving call lives in mark_logs_for_update, which is the choke point every log mutation routes through — it belongs there.
  • Only three of the ~7 timestamp sites survived. One is add_timestamped_log itself. The other two are in process_execution_result, and they write to WorkflowExecution.logs — not to the app log buffer.

WorkflowExecution.logs has five writers and zero readers. Every view that touches execution_details renders .jobs, .start_time or .progress; none of them read .logs.

Doing what the item literally said would have been a regression. Both arms of process_execution_result already report their outcome through wrkflw_logging, and get_combined_logs merges that store into the app buffer without deduplicating. Routing those two pushes through add_timestamped_log would have rendered each outcome twice — precisely the dual logging #128 removed — and would have failed the dual_logged_events_do_not_also_land_in_the_app_buffer test that #128 added.

So the dead field goes instead.

Changes

  • Delete WorkflowExecution.logs (crates/ui/src/models/mod.rs).
  • Delete its five writers: three struct constructors (two in app/state.rs, one in handlers/workflow.rs) and the two execution_details.logs.push(...) calls in process_execution_result.
  • Regenerate INDEX.md.

Net 12 deletions, zero insertions of logic. Exactly one [HH:MM:SS] format site now remains in state.rs, inside add_timestamped_log.

Behaviour

No user-visible change. The deleted field was never rendered, and the workflow outcome lines it duplicated continue to reach the Logs and Execution tabs through the global store.

Verification

All four CI commands pass with zero failures:

cargo fmt --check
cargo clippy --workspace --all-features -- -D warnings
cargo check --no-default-features --workspace
cargo test --workspace --all-features

Full workspace suite green, including all 68 tests in wrkflw-ui. No test changes were needed — the existing dual-logging test keeps guarding the app-side behaviour.

Follow-up

JobExecution.logs is dead in the same way: two writers, no readers. Left out of this PR to keep it scoped to what item 4 named, and because one of its writers carries real per-job executor output that deserves a check before deletion. Filed as item 5.

TODO item 3, follow-up from the review of PR #116.

The problem

The TUI keeps two log buffers: the app-local logs vec and the global wrkflw_logging store. get_combined_logs concatenates them with no deduplication, so any code path reporting an event to both sinks rendered it twice in the Logs and Execution tabs.

The ticket named one such message, "Executing workflow". Auditing the crate turned up 24 dual-logged sites, not one:

FileSites
crates/ui/src/app/state.rs12
crates/ui/src/app/mod.rs6
crates/ui/src/handlers/workflow.rs6

Every workflow execution, trigger, and reset was being double-reported.

The fix

At each site the app-side push is deleted and the wrkflw_logging call kept. This is the same resolution PR #116 applied to the Docker and Podman warnings, and it is not an arbitrary choice: the global store stamps its own [HH:MM:SS] prefix and a level glyph, so the surviving copy is the one that renders with a real timestamp and the correct badge.

add_timestamped_log now documents the one-message-one-sink rule so this does not creep back in.

Badges improve as a side effect

LogBadge::classify checks the level glyph in the same tier as the level keyword, so five lines stop misreporting their severity:

  • Workflow 'x' failed: ... was INFO, now ERROR
  • the "no workflow selected" lines were INFO, now WARN
  • Cannot trigger workflow in Success state was misbadged SUCCESS on the strength of the word in the state name, now WARN

One wording change

At the "Cannot trigger workflow in {state} state" site in app/mod.rs, the workflow name existed only in the app-side copy. The global warning now carries it, and the "Press Shift+R" hint moved to the same sink so it stays adjacent to the warning it explains rather than rendering several lines away.

Tests

  • executing_workflow_is_logged_once_across_both_sinks asserts the message never reaches the app buffer and appears exactly once in the merged view. It keys off a unique workflow name because the global store is process-wide and never cleared.
  • dual_logged_events_do_not_also_land_in_the_app_buffer pins the app-buffer delta for five more actions. reset_workflow_status legitimately keeps one app-only trace line.
  • The badge fixture in models/mod.rs gains glyph-prefixed shapes covering the classification this change relies on.

Both new tests were confirmed to fail when the duplicate push is reintroduced.

Verification

cargo fmt --check, cargo clippy --workspace --all-features -- -D warnings, and cargo test --workspace --all-features all pass locally.

Note on the second commit

chore: regenerate INDEX.md is a large diff and almost none of it belongs to this change. The committed index dates from April, against 158 files; the tree is now at 202. CLAUDE.md asks for a regeneration after code changes, so it is included, but kept as a separate commit so it can be dropped without touching the fix.

Follow-up to #116 and #119. Three log lines on the diff-filter path still rendered ??:??:?? in the Execution/Logs tabs:

  • the two per-warning / per-parse-error sub-items in check_diff_filter_results
  • the "Diff filter: evaluating triggers" line in spawn_evaluation

Why they were left behind

process_log_entry extracts the timestamp from the [HH:MM:SS] prefix and then trims the content that follows. The sub-items encoded their nesting as two leading spaces, so timestamping them would have flattened them against the summary line they hang under. #116 kept the indentation and dropped the timestamp.

That's a false choice. A printable glyph survives the trim where whitespace cannot, so the nesting and the timestamp can coexist — the same trick the curl: recipe lines already use.

Changes

The timestamp fix

  • New NESTED symbol (↳, U+21B3) in crates/logging/src/symbols.rs, alongside the other TUI-only glyphs.
  • The two sub-item sites now call add_timestamped_log and mark nesting with the glyph instead of two spaces.
  • The "evaluating triggers" line now calls add_timestamped_log. It had no indentation problem, it was simply missed.

Closing the door behind it

add_log is now private. It was pub with exactly one caller — add_timestamped_log itself — and all three rounds of this bug (#116, #119, and this PR) came from a site that reached past the timestamped helper to get to it. Private makes the next attempt a compile error instead of a cosmetic bug nobody notices until a screenshot.

The filter half of the bug

Restoring the timestamps made these lines readable but not yet reachable: LogFilterLevel::matches and the badge classifier in process_log_entry kept independent keyword lists. The classifier matched lowercase warning, the filter only the uppercase WARN badge word — so a warning sub-item was badged WARN and then hidden by the Warning filter that names it.

Both now classify through a single LogBadge type, so the badge the user sees and the filter that keeps the line can no longer disagree. Two visible consequences, both corrections:

  • Lines badged INFO are now shown by the INFO filter. Previously a line had to literally contain INFO or the ● glyph, so most routine output was badged INFO and hidden by the INFO filter.
  • A line is shown by exactly one filter, the one matching its badge. Previously a line containing both error and success was badged ERROR but surfaced under Success too.

LogBadge::Plain keeps the dim styling the default branch had, distinct from the INFO accent used for active work, so the panes look the same as before.

Unblocking CI

The Clippy job was already failing on main — current stable clippy flags useless_borrows_in_formatting, for_kv_map, and question_mark in wrkflw-runtime and wrkflw-executor, neither of which this branch otherwise touches. CI pins dtolnay/rust-toolchain@stable with no rust-toolchain.toml, so this appeared without any code change. Six mechanical fixes are folded in here so the pipeline can go green.

Testing

  • check_diff_filter_results_timestamps_nested_sub_items drives the real handler and asserts every line in the burst parses to a real timestamp, that sub-items carry the glyph after the renderer's trim, and that summary lines are not marked nested.
  • spawn_evaluation_logs_timestamped_line covers the evaluation-start line through toggle_diff_filter.
  • leading_whitespace_is_trimmed_but_a_nesting_glyph_survives pins the parser behaviour that motivates the glyph, so the reasoning does not have to live only in a comment.
  • every_filter_shows_the_lines_it_badges walks a representative set of real log lines and asserts each is shown by exactly one filter, the one named after its badge. This is the invariant LogBadge exists to hold.
  • diff_filter_warning_lines_survive_the_warning_filter ties that invariant back to the real handler: the warning sub-item must pass the Warning filter, the parse-error sub-item the Error filter.

cargo fmt --all --check, cargo clippy --workspace --all-features -- -D warnings, and cargo test --workspace --all-features are all clean locally.