Skip to content

Commit 39ccf5f

Browse files
committed
docs: the changelog covers every tag, and a test keeps it that way
CHANGELOG.md says of itself that it is derived from the tags rather than written from memory. It was not. slice/002 and slice/025 had no entry: both were merged on a day when other work merged too, and both were missed. Nothing failed, because nothing was checking. A public record of delivered work that is quietly incomplete is worse than one that admits a gap, because a reader cannot tell which kind they are holding. So the two entries are written, 080 and 090 are added, and a test now reads git tag -l 'slice/*' and requires an entry for each. The test refuses to pass when the checkout has no tags. actions/checkout at its default depth of one fetches none, and a test that finds nothing to check is indistinguishable from one that found everything in order; the postgres leg therefore checks out with fetch-depth 0, as the gate and fips legs already did. The direction is one-way on purpose. Every tag needs an entry; an entry without a tag is fine, because a slice's entry is written in the pull request that closes it and lands before the tag exists. mix gate: 679 passed, 18 excluded; coverage 80.31%, unchanged. Signed-off-by: Ayla Croft <aylacroft@proton.me>
1 parent 022e626 commit 39ccf5f

3 files changed

Lines changed: 100 additions & 0 deletions

File tree

‎.github/workflows/gate.yml‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,11 @@ jobs:
8282
DATABASE_URL: postgres://trinity:trinity@localhost:5432/trinity_test
8383
steps:
8484
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
85+
with:
86+
# `test/changelog_test.exs` reads `git tag -l 'slice/*'` and refuses to pass without
87+
# tags, because a test that finds nothing to check is indistinguishable from one that
88+
# found everything in order. The default depth of one fetches no tags.
89+
fetch-depth: 0
8590

8691
- uses: erlef/setup-beam@54075bcc5e249e4758d363f27d099f55d843f124 # v1.24.1
8792
with:

‎CHANGELOG.md‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,55 @@ evidence for each increment is retained by the maintainers and summarised here.
2222
tree, engineering rules consolidated into `docs/03-conventions.md`, and this changelog added as
2323
the public record of delivered work.
2424

25+
## 2026-09-24
26+
27+
### `slice/090.1` — Observability and the cost ledger
28+
Every LLM call, tool call, approval, session transition, gateway message and budget refusal emits a
29+
`:telemetry` event from a single catalogue (`docs/telemetry.md`), written before the emitters rather
30+
than documented after them, under one rule over the whole set: no prompt text, no completion text,
31+
no tool arguments and no key material. A turn carries a trace identifier into the task that runs it,
32+
so a turn reads as a tree without a tracing library and without a new dependency. A primary Logger
33+
filter redacts credentials by shape rather than by name, so a secret nobody thought to list is still
34+
caught, and it rewrites a message rather than dropping it. The cost ledger reads the usage rows the
35+
tree already writes, by day, model, session and persona, against budgets that warn and, when
36+
configured to, refuse. `/activity` shows the live event stream with the spend beside it, and a
37+
LiveDashboard page lists running sessions by asking each process its state rather than reading a row.
38+
OpenTelemetry was assessed and deferred against a measured need rather than added because the
39+
criterion named it; the reason is recorded with the slice.
40+
41+
**Tagged `slice/090.1`, not `slice/090`.** The `slice/090` tag was pushed before its merge had
42+
landed and points at slice 080's merge commit. This repository's ruleset forbids moving or deleting
43+
a tag, so the error is permanent and public; the correct tag carries the correction in its own
44+
annotation rather than leaving a reader to reconcile the two.
45+
46+
### `slice/080` — Subagents and delegation
47+
The assistant can hand a bounded piece of work to a child session through a `delegate` tool. The
48+
child has its own context and its own history, and hands back a result as text: the parent's
49+
conversation never contains the child's messages, which is the property that makes delegation worth
50+
having. Several children run at once under a concurrency cap, each with a budget in turns, tokens
51+
and wall clock; exceeding it stops the child and says so. A child that dies does not take its parent
52+
with it, and the parent is told; cancelling a parent cancels the tree beneath it. Approvals a child
53+
raises appear on the same permissions page as any other, named with the child that raised them, and
54+
a `/subagents` panel shows what is running with a stop control for each.
55+
2556
## 2026-09-23
2657

58+
### `slice/025` — Encryption at rest, and the key-custody seam
59+
The database, the receipt chain and the key material can be sealed at rest, and the seam that holds
60+
the keys is named rather than assumed. Sealing is a deployment decision with the trade-off stated in
61+
both directions: a key lost is data lost, so the custody options are documented with their recovery
62+
paths, and the default is the one whose failure mode an operator can survive. What the seam does
63+
*not* cover is written down beside what it does.
64+
65+
### `slice/002` — Supply chain, early
66+
A CycloneDX bill of materials is generated by the quality gate on every commit and ships beside
67+
every packaged binary, so whoever holds an artifact holds the list of what is in it; the bill states
68+
its own coverage inside the document, including what it does not cover. Every packaged binary
69+
carries build provenance attested through the workflow's own identity and recorded in a public
70+
transparency log, verified in the same run that produced it. A minimum TLS version is enforced for
71+
outbound connections. Brought forward from M9 because a supply-chain claim made late is a claim
72+
made about work already done.
73+
2774
### `slice/070` — Gateway core
2875
Trinity becomes reachable from channels other than the desktop. An adapter behaviour carries text
2976
and nothing else; a router admits the sender, applies a rate limit, dispatches commands and binds

‎test/changelog_test.exs‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# SPDX-FileCopyrightText: Sudo Apt Holdings LLC
2+
# SPDX-License-Identifier: Apache-2.0
3+
defmodule ChangelogTest do
4+
@moduledoc """
5+
`CHANGELOG.md` says of itself that it "is derived from the tags themselves
6+
(`git tag -l 'slice/*'`), not written from memory". This test is what makes that sentence true.
7+
8+
It was not true when the test was written. Two tags, `slice/002` and `slice/025`, had no entry:
9+
both were merged on a day when two other things were also merged, and both were simply missed.
10+
Nothing failed, because nothing was checking. A public record of delivered work that is quietly
11+
incomplete is worse than one that admits a gap, because a reader has no way to tell which kind
12+
they are holding.
13+
14+
**Why the empty case is a failure rather than a skip.** A checkout without tags - `actions/checkout`
15+
at its default depth of one - can run this test and find nothing to check, and a test that passes
16+
because it had nothing to look at is indistinguishable from one that passed because the tree was
17+
right. Every CI leg that runs the suite therefore checks out with `fetch-depth: 0`, and this test
18+
refuses to pass without tags rather than quietly agreeing.
19+
20+
The direction is deliberate: every tag must have an entry, and an entry with no tag is fine. A
21+
slice's entry is written in the pull request that closes it, which lands before the tag exists.
22+
"""
23+
use ExUnit.Case, async: true
24+
25+
@changelog "CHANGELOG.md"
26+
27+
defp tags do
28+
{out, 0} = System.cmd("git", ["tag", "-l", "slice/*"])
29+
out |> String.split("\n", trim: true) |> Enum.sort()
30+
end
31+
32+
test "the checkout has the tags this test reads" do
33+
assert tags() != [],
34+
"`git tag -l 'slice/*'` found nothing, so this test cannot check anything. A CI job " <>
35+
"running the suite must check out with `fetch-depth: 0`; a local clone has the tags " <>
36+
"already. Passing here without tags would be a pass that measured nothing"
37+
end
38+
39+
test "every slice tag has an entry in the changelog" do
40+
text = File.read!(@changelog)
41+
42+
missing = Enum.reject(tags(), &String.contains?(text, "`#{&1}`"))
43+
44+
assert missing == [],
45+
"#{@changelog} has no entry for #{Enum.join(missing, ", ")}. The file states it is " <>
46+
"derived from the tags; add the entry rather than the exception"
47+
end
48+
end

0 commit comments

Comments
 (0)