Skip to content

v0.23.0

Choose a tag to compare

@Jason-Vaughan Jason-Vaughan released this 08 Sep 05:10
· 16 commits to main since this release
0b413f3

Added

  • TangleBrain now tells you when the v0.21.0 state move left your usage log short
    (#197). Every command checks, at
    startup, whether ~/.cache/tanglebrain still holds usage records the current log does not, and
    says so on one line of stderr if it does. The fix for the underlying race shipped separately; it
    stops the truncation happening again and does nothing for a store where it already happened,
    which had no signal at all — the re-run guard is "does the destination exist", so no later run
    ever retried a short file, and the lifetime spend-avoided figure understated in silence.

    This release only reports. It does not repair, and it never writes to your data. Putting the
    legacy records back means merging them underneath everything logged since the move; a copy
    would delete that history. Repair is therefore an explicitly invoked command with a dry run, in a
    later release. Until then the one thing to do is what the notice says: keep
    ~/.cache/tanglebrain.
    It holds the only copy of the missing records, and nothing can be
    recovered from it once it is deleted.

    Records are compared, never file sizes — after the move the current log grows on every run while
    the legacy one is frozen, so a log truncated at migration and used for a week is larger than the
    file it is missing records from, and a size check calls that healthy. It allows for compaction
    too, which folds the oldest rows out of a healthy log and would otherwise make a heavy user's
    intact store look robbed; where a fold has gone deep enough that the two cannot be told apart, the
    notice says so rather than claiming data loss.

    What it costs, and two things it deliberately does not do. An intact store whose log has not
    folded past the migration boundary costs two small reads, independent of log size; after such a
    fold every command pays a scan, permanently, and an inconclusive notice repeats on every
    invocation with no flag to silence it — suppressing it would mean remembering it fired, and
    remembering means writing to the store. A legacy root you have already deleted cannot be
    reported on at all, being indistinguishable from a machine that never had one. And a legacy log
    that cannot be read says that, rather than letting an unreadable file look like a clean bill.

    The full reasoning — why each of those holds, and what the detector rests on — is in
    docs/design/observability.md § Migrated-log integrity, so it stays in one place as the detector
    changes.

Changed

  • The knob panel is two views behind a sidebar, not one scrolling column (#166).
    Chat holds the run box; Settings holds the spend-avoided rollup, the roster table and the
    pricing reference. Views are addressable (#/chat, #/settings), so the browser's back button
    and a bookmarked view both work.

    The single column was the right shape for a read-only dashboard and is the wrong one for what
    comes next — a chat feed, roster modals, execution traces. Nothing about what the cards contain
    changed and no endpoint moved; this is layout, and the panel is still one packaged file served by
    a stdlib handler with no framework, no CDN and no build step, so it still renders with the machine
    offline.

    The nav is the panel's first interactive navigation surface, so it ships keyboard-operable: real
    anchors, a visible focus ring, a skip link, and aria-current on the active view rather than
    colour alone. The router owns the #/ prefix and nothing else, so a plain in-page anchor — the
    skip link is one — moves the fragment without moving the reader out of the view they are in.

Fixed

  • tanglebrain-gui --help called the panel "read-only". It has not been read-only since the
    pricing and roster cards became editable — it writes pricing.yaml and roster.yaml. The help
    now says what the panel actually does.

  • A security report had nowhere correct to go. CONTRIBUTING.md forbade opening a public issue
    for a vulnerability and pointed at the contact in CODE_OF_CONDUCT.md — which is the
    conduct-enforcement address, offered there for harassment reports. A reporter who followed the
    trail landed on the wrong channel; if they guessed it doubled as security, the report arrived as
    plain email rather than a private advisory, with no embargo and no acknowledgement path. Worse
    than an empty pointer, because it looks answered. The private route now lives in the mirrored
    security rules and that line points at it.

  • Two console scripts starting at once could corrupt or lose the state migration
    (#139). Every console script migrates
    the pre-0.21 state root on startup, and each staged its copy under a fixed name — so both
    processes computed the same staging path, and each could act on the other's file. Two
    interleavings were possible, and the quiet one is the worse:

    • Loud. The second process fails part-way and cleans up the shared path, deleting the
      completed copy the first had just staged; the first's move into place then fails on a file
      that is no longer there. Both processes print could not move state and nothing migrates,
      telling the operator their history is stranded and --stats may read low, on a machine where
      nothing is wrong. The next run does repair it.
    • Silent, and permanent. The second process's cleanup unlinks the first's staging file
      while it is still being written, then creates a new one at that same path. The first
      process finishes writing into the file it no longer has a name for, and then moves the second
      one's still-partial copy into place. Nothing raises, nothing is printed, and the migration
      reports success — but because the re-run guard is "does the destination exist", the truncated
      file is never retried and understates the lifetime figure for good.

    If you installed 0.21.0 or 0.22.0 and --stats reads lower than you expect, compare
    ~/.local/share/tanglebrain/usage.jsonl with ~/.cache/tanglebrain/usage.jsonl. The migration
    copies and never deletes, so the original is still there: delete the short file at the new path
    and run any tanglebrain command to migrate it again. If you also find a file named like
    usage.jsonl.<hex>.tmp there, it is an abandoned staging copy from an interrupted migration —
    inert, read by nothing, and safe to delete.

    Staging now uses atomic.staging_path, which is unique per process, so no process can reach
    another's staging file at all. Cleanup moved into a finally in the same change: a unique name
    is unfindable by any later run, so an interrupt — Ctrl-C during the first-run migration — would
    otherwise leave a staging entry in the data root permanently.

    Found and reported by @be-student, who also identified atomic.staging_path as the fix. As
    CONTRIBUTING.md explains, external contributions are re-implemented rather than merged: the
    diagnosis and the approach are theirs, the bytes here are ours.

Internal

  • CONTRIBUTING.md's mirrored security rules now match upstream again. The Clean Room clause
    said a reconstruction "credits you as the author", which pulled against the standard it sits in —
    the whole point of the clean room is that we author the bytes. Upstream
    Jason-Vaughan/.github
    resolved it to "ensures you receive full credit and an @mention for your contribution (while
    preserving the Clean Room distinction that we author the final bytes)", which promises the
    contributor more, not less, and no longer contradicts the paragraph above it. This copy is a
    byte-for-byte mirror, so it follows rather than diverges. One sentence; no other line moved.

  • The weekly CI canary's comment now names both extras it resolves — ".[delegate,dev]", not
    ".[delegate]". The job runs make test, which builds the venv from pip install -e ".[delegate,dev]", so the canary has always resolved ruff and mypy too and would catch an
    upstream break in either. The comment understated its own coverage in the direction that costs
    something: a reader deciding whether the dev toolchain needs a separate watch was told it was
    uncovered when it was not, and the likely result is machinery built to solve a solved problem.
    Comment only; no workflow behaviour changes. Closes
    #146.

    Credit to @7487, who found the same discrepancy and opened
    #167 against it. That PR was not merged
    because external contributions are re-implemented rather than merged and because it modified
    .github/workflows/, a boundary this project enforces — a boundary that, as it happens, was only
    published in CONTRIBUTING.md earlier today. The observation was theirs; the bytes here are ours.

  • CONTRIBUTING.md now mirrors the full seven-point security checklist from
    Jason-Vaughan/.github,
    replacing the five-bullet condensation that predated it. Contributors are now told, in the
    project's own words, that CI workflows and build scripts are off-limits, that no new dependency
    may be added, and that obfuscated content is an immediate ban — three rules this repo already
    enforced without publishing.

    Extracted programmatically and verified byte-identical between the mirrored-security-rules
    markers, so the drift checker reads what upstream wrote. Two repo-specific lines the upstream
    block does not carry — the vulnerability-reporting route and the pointer back to the full guide —
    moved outside the markers rather than being dropped: inside, they would have broken the
    byte-identity the mirror exists to guarantee.

  • CONTRIBUTING.md carries the account-wide security rules, byte-identical to the copy in the
    other repositories and extracted programmatically rather than retyped, between the
    mirrored-security-rules markers a drift checker
    (.github#1) reads.

    A repository shipping its own CONTRIBUTING.md inherits none of the account-wide one —
    GitHub's community-health fallback is per-file and all-or-nothing — so without this the standards
    reached every repository except the ones that take pull requests.