mirror of
https://github.com/gastownhall/beads.git
synced 2026-09-14 20:17:24 +08:00
main
28 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
c5e49a3b78 |
docs(config): document audit.enabled in configuration reference (#5940)
The optional JSONL interaction sidecar at .beads/interactions.jsonl is gated behind audit.enabled (default false, internal/config/config.go:245) with a BD_AUDIT_ENABLED env override (internal/audit/audit.go:67-72), but the key is absent from docs/reference/configuration.md, so there is no documented way to discover it outside `bd audit` help text. Two changes, both on the same page: - Add an `audit.enabled` row to the Tool-Level Settings table covering the default, the env var, what enabling it does, the fact that both `bd audit record` and `bd audit label` refuse to write while it is off (both go through audit.AppendIfEnabled -- cmd/bd/audit.go:103 and :146), and the fact that issue history is recorded in the database regardless. - Add `audit.*` and `storage-class.*` to the list of namespaces routed to config.yaml. Both are in config.IsYamlOnlyKey's prefix list (internal/config/yaml_config.go:118) but were missing from the doc; the list now matches that prefix list exactly. Reported by @treystout in #5904. |
||
|
|
67556264c4 |
feat(events-journal): record the mutating actor on every journal row (#5908)
* feat(events-journal): record the mutating actor on every journal row (bd-mbrxm) The bd_events_journal row recorded op/ts/issue_id/payloads but never WHO performed the mutation, so downstream attribution (e.g. same-field, different-actor conflict detection over the journal) had nothing to key on. - Schema: `actor VARCHAR(255) NOT NULL DEFAULT ''` on bd_events_journal, on both migration planes (0066 + ignored/0025) so the two-door shape converges. - Record*InTx takes the actor explicitly (no ambient ctx) — every call site already sits beside an audit-event emit that has the actor in hand; thread that same value (incl. the UOW door's MovePersistence). Comment rows carry the comment's author. Blank = unattributed (derived maintenance, deletes — follow-up bd-zxt49 — and pre-column rows); never user attribution. - `bd events tail/export` JSON gains an additive, omitempty `actor` field; the wire vocabulary is otherwise untouched (openapi spec, reference doc, CHANGELOG; golden regenerated deliberately). - CLI bundle: direct-DDL override for 0066 in cliCompatibleMigrationSQL — dolt 2.2.0 (CI's pin) no-ops the prepared ALTER (dolthub/dolt#11345), so without it the parity oracle sees actor only on the runtime side. Measured: AllMigrationsSQL() through 2.2.0 lands bd_events_journal WITHOUT actor and 2.3.1 WITH; with the override both land it. String assertions + a requireDoltColumnShape guard pin it without a Dolt binary. Verified (enumerated): go build ./...; gofmt; go vet; migration hygiene (BASE_SHA=07c163df0); internal/storage/schema (string test + CLI-apply test under dolt 2.3.1 AND 2.2.0); internal/storage/domain/...; eventsjournal; journalops; httpapi (wire-tag bijection incl. actor); storage/dolt -run 'Journal|EventsJournal|ActorAttribution' against real dolt; cmd/bd -run Events (golden); storage/uow -p 1 (-timeout 90m): every test green except 3 timeout-shaped reds (ConcurrentInstantiation lock-acquire, EndToEnd 30s init-schema, PreexistingDirty docker reaper) in a /private/tmp worktree under box load 40-68; the same 3 at origin/main in that worktree also red, and at THIS commit in the primary clone all 3 PASS (130s) — box flake, not the diff. * docs(events-journal): align seq:4 example with the actorless golden fixture Maintainer review follow-up on PR #5908 (bd-mbrxm). Both reviewers flagged that the reference JSONL example's seq:4 is_blocked derived-maintenance row carried "actor":"worker-1", contradicting the record-contract field table (derived is_blocked recomputes are actorless) and the golden fixture cmd/bd/testdata/events_journal_records.jsonl, which emits that same seq:4/bd-100 row without an actor. Drop the actor member so the doc example mirrors the fixture byte-for-byte and consumers do not misclassify a system recompute as a user write. The other, optional review nit (the "stray typographic quote" in the insertEventRow doc comment) is intentionally left as-is: that glyph is gofmt's canonical rendering of '' in a Go 1.19+ doc comment (TeX-style quoting turns two ASCII apostrophes into U+201D), so the reviewers' literal '' suggestion is a no-op the enforced pre-commit gofmt reverts on every commit. It is cosmetic and non-gating; the DDL and INSERT are already correct. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Eddie the Engineer <julianknutsen@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
9a1f086a41 |
docs: pin the Dolt version instead of steering users to releases/latest (ga-m0dlk) (#5899)
The install instructions told users to run the upstream install.sh from
releases/latest, which today resolves to Dolt 2.3.1. Dolt 2.3.0 regressed
CALL DOLT_RESET('--hard'): a few percent of freshly created databases come
up with that procedure answering "Error 1105 (HY000): context canceled" for
the life of the server process. bd flatten and the Dolt-history compaction
in bd admin compact both finish by hard-resetting main onto a temporary
branch, and the merge-settle path behind bd dolt pull falls back to a hard
reset when it abandons a merge — so the documented happy path installed a
Dolt that breaks those operations on a database that otherwise looks fine.
Point the docs at a pinned version instead, with the measurements and the
raise criterion, and tell anyone already on 2.3.x how to check a database
and what to do about it. The pin and its rationale live in
scripts/ci/install-dolt.sh; the docs name the version once, on the Dolt
page, and everything else links there.
Also note that releases/latest resolves to the most recently created
release rather than the highest version, so it can move backwards — v1.88.2
was created after v2.2.4 and v2.3.0 — which makes "install latest" unsafe
independently of this defect.
test/docsync/doltpin_test.go keeps the documented pin, the copy-paste
install snippet and testutil.DoltDockerImage from drifting apart, which is
how the docs ended up on a broken release in the first place.
This documents the pin that gastownhall/beads#5894 applies to CI; land
after it so the reference to scripts/ci/install-dolt.sh resolves.
|
||
|
|
aa8cc065f4 |
docs(troubleshooting): document the two native-Windows integration traps (#5838)
* docs(troubleshooting): document the two native-Windows integration traps bd.exe is a native Windows binary, and two consequences of that are undocumented. Both cost real debugging time because neither fails loudly. - npm installs bd as a generated bd.cmd shim, so Node's execFile/spawn -- which run their target directly, without a shell -- never resolve it and die with "spawn bd ENOENT" while bd works in the same terminal. - Git Bash resolves /tmp to its own temp directory; bd, being native, resolves it against the current drive root. `bd export -o /tmp/x.jsonl` reports success and writes C:\tmp\x.jsonl. Adds a subsection for each under Platform-Specific Issues, following the symptom/solution shape already used there, plus cross-links from the FAQ Windows entry and the installation Windows notes. Agent-Signature: claude-code-claude-opus-5[1m]-unknown-reasoning on behalf of CuongBPV * Update troubleshooting for Windows `/tmp` path issues Clarify behavior of `/tmp` paths in Git Bash and provide solutions for Windows path handling. * Update FAQ for Windows integration details Clarify integration details regarding `bd.exe` and `/tmp` paths in Windows. --------- Co-authored-by: CuongBPV <cuongbpv@fpt.com> |
||
|
|
d2fc47949d |
feat(lint): configurable per-type lint sections via lint.sections.<type> (#5855)
* feat(lint): configurable per-type lint sections via lint.sections.<type> bd lint, bd create --validate, doctor conventions, and markdown import all check issue descriptions against built-in required sections that are hardcoded per issue type. This adds a config knob: bd config set lint.sections.epic "Standards scorecard, Cost" - lint.sections.<type> holds a comma-separated list of section headings, stored in config.yaml (yaml-only key, same path as validation.*). - The list is ADDITIVE to types.IssueType.RequiredSections: built-in requirements are never relaxed, only extended. - Entries are normalized to "## <heading>" (any leading '#' run and surrounding whitespace stripped) and deduped case-insensitively against both the built-ins and each other, so re-declaring a built-in changes nothing. - Consumed in internal/validation.ValidateTemplate (the single choke point behind LintIssue and every lint consumer) via mergeLintSections. - lint.* is a recognized config prefix and documented in bd config and bd lint help. * docs(config): list the lint.* namespace and lint.sections.<type> in YAML-only keys Address review feedback: the PR routes lint.* through config.yaml (internal/config/yaml_config.go) but the YAML-only Keys section of docs/reference/configuration.md did not list it. Add the namespace and a one-line key description. (The pre-existing audit. and storage-class. omissions are left as-is, out of scope for this PR.) |
||
|
|
21834e720d |
feat(doltversion): dolt CLI discovery, hardened version probe, and warn-only version policy (contract part 1) (#5092)
* feat(doltversion): own dolt CLI discovery, hardened probe, and version policy in a leaf package
PR-1 of the dolt CLI install/version contract (design:
mybd reports/2026-07-25-psxg4-dolt-cli-contract-design.md). The dolt
executable managed proxied-server mode spawns was discovered by a bare
exec.LookPath with no override, probed nowhere, and version-checked only
by one advisory archive_level probe; a missing binary erred after
.beads/ creation and an incompatible one died opaquely.
internal/doltversion (stdlib-only) now owns:
- resolution order BEADS_DOLT_BIN env > clone-local sidecar (hook point;
the sidecar itself lands with identity threading in PR-2) > PATH.
Explicit values are absolutized before validation (a bare name would
otherwise validate the cwd file but exec a PATH lookup) and an
explicitly-named broken binary is an error, never a silent
fall-through;
- a hardened probe: timeout + process kill, 64KiB output caps,
unconditional symlink resolution recording given and real paths,
errno-based exec-format diagnostics naming the likely arch mismatch,
and file identity captured for later change detection;
- version parsing generalized from the archive_level probe, tolerating
prerelease/dev suffixes;
- policy: NO hard version floor (adversarial review rejected 2.2.0 as
policy-without-evidence); RecommendedMin 2.0.0 warns only, backed by
the 2026-07-25 cross-version matrix run (cross-read of module-written
storage requires >= 2.0.0). An unparseable version string is likewise
a warning, not a refusal - the only hard failures are a missing,
non-executable, or probe-failing binary.
Wired at the two entry points: the managed proxied UOW factory replaces
its bare LookPath and derives archive-level support from the probe it
already ran (no second dolt-version fork on the per-command hot path;
warning is printed once, suppressed under --quiet/--json), and bd init's
proxied preflight fails before any .beads/ write when the binary is
missing or broken (external proxied config is exempt - bd never spawns
that server). doltserver.Start's own bare LookPath is deliberately
untouched here; identity threading through every spawn site is PR-2.
Agent-Signature: claude-fable-5-medium on behalf of matt wilkie
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AQHiBWVPSXDCcUSG9EGw9d
* fix(doltversion): fail closed on prerelease archive_level; dedupe dolt advisory
Two review findings on this PR (see reports/2026-07-26-reviewer-dispatch-sweep.md
#5092 for the adversarial-review reasoning):
1. archive_level fail-open regression (major, re gastownhall/beads#4986).
newManagedProxiedServerUOWProvider derived archiveLevelSupported straight
from doltID.Version.AtLeast(MinDoltVersionForArchiveLevelConfig), but
AtLeast deliberately ignores Version.Prerelease (by design, for the
separate warn-only RecommendedMin check). That meant a prerelease build
at or above the floor, e.g. "1.52.1-rc1", satisfied the >= 1.52.1 gate
and had auto_gc_behavior.archive_level written into the YAML — even
though the old doltVersionAtLeast (gc_config.go) already failed closed
for any prerelease-suffixed string via strconv.Atoi. If that prerelease
build predates the field, dolt sql-server's yaml.UnmarshalStrict refuses
to start: the exact #4986 regression. Extracted the derivation into
doltArchiveLevelSupported(id) and added a Prerelease == "" gate, plus
TestDoltArchiveLevelSupported (table test covering above/at/below the
floor and prerelease-at/-above-floor) and
TestDoltArchiveLevelSupported_UnparsedVersionFailsClosed.
2. Duplicate dolt-version advisory (major, UX). `bd init --proxied-server`
probed and printed the version-recommendation warning twice: once in its
own preflight (init_proxied_server.go) and again ~90 lines later when
newProxiedServerUOWProvider reaches newManagedProxiedServerUOWProvider,
which resolved+probed again. The existing doltVersionWarnOnce didn't
cover init's own Fprintf. Extracted one shared resolveAndProbeDolt(ctx,
errPrefix) helper (uow_factory.go) that does resolve + probe +
once-gated warn, called from both sites; init_proxied_server.go no
longer needs its own copy of the Resolve/ProbeWithPolicy/error-wrapping
sequence. This also folds the two call sites' warning gates (previously
!in.quiet vs !quietFlag && !jsonOutput) into one JSON-mode-safe gate.
Verified: CGO_ENABLED=1 go build -tags gms_pure_go ./... clean;
CGO_ENABLED=1 go vet -tags gms_pure_go ./cmd/bd/... clean; gofmt -l on all
changed files empty; targeted tests pass (TestDoltArchiveLevelSupported,
TestDoltArchiveLevelSupported_UnparsedVersionFailsClosed,
TestNewProxiedServerUOWProvider_RoutesExternalConfigToExternalProvider,
TestNewExternalProxiedServerUOWProvider_*, TestBuildProxiedServerClientInfo,
and internal/doltversion's own suite).
Agent-Signature: builder-unknown-model-unknown-reasoning on behalf of maphew
* test(doltversion): resolve symlinks in RealPath expectations (macOS fix)
TestProbeNormalOutput and TestProbeSymlink failed on macOS CI because
t.TempDir lives under /var/folders, itself a symlink to
/private/var/folders. Probe derives RealPath via filepath.EvalSymlinks,
so it correctly returned the /private/var form while the tests compared
against the raw TempDir path.
Map the expected path through the same EvalSymlinks resolution (new
resolveSymlinks test helper). Verified against a symlinked TMPDIR on
Linux, which reproduced the failure before this change.
Agent-Signature: claude-fable-5-high on behalf of maphew
* refactor(cmd/bd): narrow resolveAndProbeDolt to drop the unused identity return
Upstream removed the archive-level gate from proxied-server mode
(
|
||
|
|
766e6c17f2 |
fix: reject incompatible timeout in generated hooks (#5522)
Select timeout or gtimeout only after a successful GNU coreutils identity probe, validate BEADS_HOOK_TIMEOUT as positive whole seconds, and preserve the Perl and warned direct fallbacks under inherited shell options. Keep GNU and Perl deadlines soft and backend-scoped, preserve natural status 137, regenerate every tracked hook artifact, and exercise the real process boundary through the required three-host preflight. Fixes #5503 Agent-Signature: codex-gpt-5-unknown-reasoning on behalf of Ewen Cuthiell |
||
|
|
ff75b45c91 |
docs: the ext-api-store documentation tranche — post-#5516 auth sweep, wave doc coherence, role checklist refresh (#5545)
Three beads (ga-uwix0.1, ga-2yaqp.5, ga-2yaqp.6) plus a fable review round. Docs and spec prose only: all 19 touched .go files are gofmt-normalized, comment-stripped byte-identical to main. The one exception is deliberate and reviewed — scripts/check_doc_freshness_test.go gains a data row, because the delegated gate ruling required it. ga-uwix0.1 — #5516 gave bd serve optional bearer auth and made it MANDATORY beyond loopback; fifteen prose sites still said the opposite. The three the council named are reworded to the stronger true form (the actor is not the authenticated principal even where a bearer is required; a shared token admits a client to the WHOLE surface and grants no narrower right). The sweep for a fourth found eight more in five spellings the audit's grep could not reach, including claim.go's "any client can claim as any name", release.go's no-AUTHORIZATION variant, and six memory/settings-plane sites where the true statement is sharper: a bearer decides WHO may call, never what a caller who is let in may read. The review round found four more in the CHANGELOG's UNRELEASED section, which round one had misclassified as released history — they would have shipped false in the same release as the auth entry. The serve runbook's "Binding beyond loopback" section was actionably wrong: it said the flag "prints a warning and changes nothing else", and its refusals table quoted a string not in the binary. Rewritten against a build, with every flag, error string, event name and reason value produced by running the command. It gains the operational trap nobody had written down: /healthz is auth-exempt and GET /v0/beads/ready is not, so an unauthenticated readiness probe earns a 401 forever while liveness stays green. The runbook is now IN scripts/check-doc-freshness.sh, pinned to the four files it is read against. ga-2yaqp.5 — nine prose defects, every count re-derived rather than trusted; three of the audit's own numbers had moved again. 38 route rows and 36 capability-bearing ones; thirteen pinned schemas, not twelve or seven; six missing entries in the no-query-parameter list, not five; twenty-eight role accessors, not twenty-seven; nineteen patch members, not thirteen. Two prose "code counts" were counting paragraphs. CodeAlreadyClaimed claimed the holder is always attached when only one of four producers always attaches it and releaseIssue never does. MetadataCAS's leaf contradicted its own shipped wire. The path/operationId inversion is now recorded where a spec author looks. ga-2yaqp.6 — the role checklist refreshed against what this wave's additions actually cost, traced to real commits. Step 10 sent people to a name list #5460 deleted. The "four more edits" claim was stated twice and undercounted both times: the two freshest wire slices share a nine-file core plus roles_test.go. The step-with-no-number gained the fact that explains its instances — write-role decorators wrap, read-role decorators recurse, so the call lands on the stub, and both real instances are reads. The wave got its first CHANGELOG entry: six new public operations, not three, and zero new role accessors in the window. Review verdict: FIX-REQUIRED then all items addressed. The reviewer live-falsified the strong auth form and failed to break it, and confirmed every recount re-derives exact. |
||
|
|
d59200bd39 |
refactor: delete the dead orphan-handling capability (bd-gwryr) (#5497)
BatchCreateOptions.OrphanHandling had four modes and one behavior. Every production call site passed OrphanAllow (or left the field at its zero value, which took the same branch), so CheckOrphan returned (false, nil) on every path it could reach — after paying a SELECT COUNT(*) parent-existence probe on every hierarchical-ID create to feed a switch whose only reachable arm ignored the answer. OrphanStrict and OrphanSkip were reachable from exactly one conformance case, testAuditCreateOrphanHandling, which existed to execute them. OrphanResurrect was never a branch at all: it shared the default arm with OrphanAllow, and the "auto-resurrects missing parents from database history" behavior its doc comment described left with the JSONL sync layer. internal/beads' TestImportWithDeletedParent still asserted that removed behavior; it goes too. This removes the type, the four constants, the field, CheckOrphan and its call site, the backend package's alias and re-exports (a breaking change to a published package, signed off by the owner), the dead ImportOptions.OrphanHandling, and the import.orphan_handling row in the yaml-only-key test — replaced with import.unlisted-key, which keeps the "import.* is exact-match, not a prefix namespace" assertion the row was actually providing. The behavior that remains is what OrphanAllow always did: a hierarchical create whose parent is missing is accepted, and the child's auxiliary counter row is skipped because it would have no owner (TestReconcileChildCountersSkipsMissingParent pins that). The wasted query is gone from the create path. Co-authored-by: CI Bot <ci@beads.test> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
19e4af709a |
feat(httpapi): stream the events journal over SSE (bd-opisf)
GET /v0/beads/events:watch holds a text/event-stream response open and emits
each committed mutation as it lands, so a consumer learns about a write when it
happens instead of on its next interval. It is a SIBLING of the paged read, not
a mode of it: `follow=true` would have given one operationId two media types,
two lifetimes, two limit rules and two capacity stories, and a client generated
from the document could not have described either.
THE CURSOR IS STILL THE CONTRACT, which is what makes this cheap to publish.
The server keeps no per-consumer state and remembers no subscription; a stream
is the `since` reads a poller would have performed, performed on its behalf
against the same ReadEventsJournalPage through the same read-only cursor. Every
event carries `id: <seq>`, so the checkpoint a client holds while streaming is
the number it would have held while polling, and `Last-Event-ID` — the header a
browser's EventSource resends by itself — takes exactly the value `since` takes.
That header WINS over the parameter, because the client that sends it did not
choose to: an EventSource reconnects to the URL it was built with, so honoring
`since` there would replay every record since the consumer started, on every
reconnect, forever. A nonempty unparseable one is a 400 naming the header rather
than a silent fallback, which would start a stream somewhere the client did not
ask for and never tell it; an EMPTY one means "no id yet" and defers to `since`,
because a client that always sets the header sends it empty on its first
connect and refusing that would break the request the header exists to serve.
Delivery is at-least-once against the client's own checkpoint: exactly once and
in order within a stream, duplicates only across a reconnect. No engine or
storage change — this consumes the poll read path.
EVERY REFUSAL IS DECIDED BEFORE THE FIRST BYTE — validation, the disabled 409,
the flush check, the stream cap, and then the first read, which is what turns a
pruned-past cursor into the paged read's own 410 rather than a 200 that
immediately reports its own failure. Exactly one failure can arrive after the
status is spent: a prune racing an open stream, emitted as a NAMED `truncated`
event carrying the 410's own body in the same encoding on one line (asserted
against Write's output, not merely agreeing with it), behind a raised `retry`
and followed by a close. Naming only the exception is what lets a bare
`onmessage` consumer treat an unrecognized event as "stop, this is not a
record"; one that ignores it does not lose records silently, it stalls loudly on
connect-time 410s, slowly. The other unreportable failure — a stored payload
that will not encode — ends the stream too, and names its seq in the log,
because it will end every stream that reaches that row and nothing else would
say which one it is.
THREE LIMITS HAD TO MOVE, and the middle one is the trap.
- requestDeadline (60s) does not apply to a streaming row. For every other
operation it is the backstop against a request holding resources forever;
here it would do nothing but sever the stream. Each READ carries a 15s
deadline instead — a liveness budget rather than a database one, since it
also bounds how long a stream can go without a heartbeat and how long it can
ignore a shutdown.
- http.Server.ReadTimeout (30s) had to be cleared per request, BEFORE the
connect read. There is no WriteTimeout on this server — statusWriter rolls a
deadline forward before every write, which bounds one stalled write rather
than a stream, and needs nothing here — but ReadTimeout is a deadline on the
WHOLE request, and net/http keeps a background read running while a handler
writes: the read that notices a disconnect. Its expiry cancels the request
context, so an idle stream would end itself at thirty seconds with no client
involved, and a database wedged during the connect read would be booked as
`client_closed`. Clearing it costs no disconnect detection — a client that
leaves still produces a read error — and a peer that vanishes without a FIN
is reaped by TCP keepalive, which Go's listener enables by default
(measured: 15s idle, 15s interval, 9 probes; ~150s).
- The database slot moves from the request to the read. Sixteen slots and
forty-eight stream places means one slot per open stream would let a handful
of idle consumers starve every other operation for hours with /healthz
green; a stream now takes a slot for the moment of each poll and gives it
back, and slot pressure delays a pass instead of ending a stream.
Streams are capped at 48, SIXTEEN BELOW the 64-connection limit, and the gap is
load-bearing arithmetic rather than caution: LimitListener returns a connection
slot only when the connection closes, which is after the handler returned and
after this counter came down, so a cap at maxConns could never be observed — the
connect that should earn the 503 would never be accepted, and neither would the
poll it is told to fall back to, nor a mutation, nor a fresh-connection
/healthz. The headroom makes the refusal deliverable; `events_watch_admitted`
carries the live gauge on every connect so accumulation is visible before the
cliff. Each stream jitters its own poll phase by ±10%, because consumers
reconnect together after a restart on one shared `retry` and a fixed interval
would march them into a single read burst per second forever. And a graceful
shutdown signals its streams (RegisterOnShutdown) rather than waiting them out;
the loop consults that signal and the client on EVERY pass, including the
draining ones, so a week-old backlog stays cancellable within one batch.
Tests: handler-level over live delivery (mutations land AFTER the connect),
id=seq, heartbeats, Last-Event-ID precedence including a stale header against a
live `since` and the empty-header polarity, the 400/409/410 connect legs, a
prune under an open stream, the cap and its 503, the cap's headroom against
maxConns, backlog drains cancelled by both a hangup and a shutdown, slot release
on every exit, a stream sharing a single-slot semaphore with a paged read, the
flusher refusal, the read-deadline clear and its ordering against the connect
read, jitter spread, and a clean drain with a stream open — all under -race.
Framing is pinned against a record whose payload carries literal newlines, a CR
and a `data:` line of its own. Two e2e legs through a real `bd serve`: mutate on
one connection while streaming on another under a 10s arrival bound, then
reconnect with Last-Event-ID and require the resume, plus the disabled refusal.
Mutated and each caught: the mid-stream truncated event, Last-Event-ID
precedence, the empty-header polarity, the cap decrement, the cap's headroom,
the drain-cancel fast path, the read-deadline clear and its ordering, the poll
jitter, the shutdown exit, and the deadline exemption.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Agent-Signature: claude-code-claude-opus-5[1m]-unknown-reasoning on behalf of julianknutsen
|
||
|
|
7992c00702 |
feat(httpapi): publish the events journal on the v0 surface (bd-opisf)
GET /v0/beads/events pages the durable mutation journal from a caller-held
checkpoint, so a hosted consumer stops shelling out to `bd events tail`. The
journal and its retention contract already landed; this is the consumption
surface for them.
THE RECORDS ARE THE CLI'S RECORDS, and that is a machine fact rather than a
promise. The envelope and its projection moved out of cmd/bd into
internal/eventsjournal, beside the activation and auto-prune logic that already
had to be reachable from two binaries; the spec pins EventRecord to that struct
with x-go-type, pinning.go proves the generator emitted an alias, and the
committed golden fixture now renders through the same function the handler
serves from. A second wire struct in internal/httpapi would have passed its own
tests while quietly shipping a different `ts` shape or a `dep` that appeared
when empty, and the consumer who notices is the one reconciling an HTTP mirror
against a CLI export.
THREE ANSWERS THE DATA CANNOT TELL APART, which is most of the design:
- CAUGHT UP is a 200 with an empty list. "Nothing new yet" is an ordinary
answer about a log, and a poller whose steady state was a 404 would have to
treat this surface's miss vocabulary as its happy path.
- DISABLED is a 409 `events_journal_disabled`. A journal that is off reads as
zero rows and a head of zero — byte-identical to an enabled journal nothing
has written yet — so without this gate a consumer would poll a workspace
that will never emit a record and call itself caught up forever. Activation
is workspace state, so it is resolved by cmd/bd (eventsjournal.EnabledFor)
and handed in; this package resolves no workspace state of its own. A 409
for CodeNotClosable's reason: a statement about state the client cannot see,
which the same request stops earning once an operator changes it.
- PRUNED PAST is a 410 `events_journal_truncated` carrying `since`, `floor`
and `head`. It is the surface's only 410 and it is the right one: those
records existed, were addressable by exactly this request, and were
deliberately deleted — retrying the same `since` can never succeed, which is
what a 404 would wrongly invite. The mapping lives in ClassifyError, keyed
on the typed error with errors.As, so neither database source can forget it;
the code is storage's own constant, so the CLI's `--json` failure and this
body cannot drift to two spellings of one condition.
`head` is why the read seam grew. ReadEventsJournal answers what came next and
cannot answer "keep asking, or wait?", so ReadEventsPageInTx returns the rows
and the counter head from ONE transaction, read in that order — a head taken
first could land below the last row served, which a consumer reads as "past the
end" and stalls on. `bd events tail --follow` keeps the cheaper path: it runs
the truncation decision every second and pays for a head read only in the
ambiguous cases.
The handler holds storage.EventsJournalCursor, a read-only interface, not the
wider accessor the CLI takes. There is no prune, no acknowledge and no
truncate over HTTP: retention stays a workspace decision, and a surface
documented to publish the journal should not be one line from a delete.
EventsJournal is the first CONDITIONAL role on httpapi.Config — required
exactly when the workspace's journal is enabled. A storage backend with no
journal seam is an ordinary backend as long as nobody asked it to record
anything (eventsjournal.Apply takes the same deal at open time), and demanding
it unconditionally refused to start two registered-backend servers that had no
use for it.
`limit` runs 1..10000 defaulting to 1000, and `0` is REFUSED rather than
meaning unlimited as it does on the issue listings: a caller resuming from an
old checkpoint would otherwise ask one process to buffer the whole retained
window. `since` is required — defaulting a missing checkpoint to zero serves
the entire window to a consumer that meant to resume, which arrives as a flood
of duplicates rather than as an error.
Tests: handler-level over both refusals, the caught-up/empty/disabled trio, the
limit bounds and an int64 checkpoint; a storage-level head guard against the
real embedded store; and three end-to-end legs through a real `bd serve` —
mutate over HTTP then read the journal back gapless, prune and meet the 410 then
recover from floor-1, and a disabled workspace refusing. The truncation mapping,
the disabled gate, the window members and the limit ceiling were each mutated
and each failed a test.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Agent-Signature: claude-code-claude-opus-5[1m]-unknown-reasoning on behalf of julianknutsen
|
||
|
|
d05d946262 |
feat(events): bounded-by-default auto-prune to the retention floors (bd-opisf)
Enabling the journal now yields a BOUNDED feed. Until this commit the only
thing that deleted a record was an operator typing `bd events prune`, so the
person who turned the feature on was not the person who discovered, months
later, that a table nobody reads had become the largest thing in the database.
Retention floors already described the window a consumer is guaranteed; this
applies them without being asked.
The shape is SQLite's WAL auto-checkpoint rather than a background compactor,
with Dolt's auto-GC as the second reference: the WRITER pays, immediately after
its own commit, only when a cheap persisted threshold says work is due, and
never for more than a fixed slice of it. Neither needs a daemon, and neither can
turn a user's command into an unbounded wait. Only the trigger and amortization
pattern transfer — the journal needs no finalization data movement, so our
checkpoint analog is pure prefix deletion.
POLICY IS THE FLOORS, and it is not a second implementation.
ComputeEventsAutoPruneBoundInTx hands the EXISTING resolver an unbounded request
(before = head+1) and keeps whatever the floors leave, through the same two
scalar reads `bd events prune` uses — now factored into one place so a floor
cannot mean one thing when an operator asks for it and another when maintenance
applies it. Both floors disabled means the operator chose an unbounded ledger,
and auto-prune becomes a no-op that does not even read the throttle.
TRIGGER is the root PersistentPostRunE maintenance net, last after Dolt
auto-commit, auto-backup, auto-export and auto-push, before the store closes.
That is the narrowest existing seam with the four properties this needs: the
command succeeded (cobra never reaches PostRunE on an error return), its
transaction is closed, a plumbing is still open, and the read classification is
already made there. It fires at most once per process. Four gates keep it
writer-pays, each one load-bearing rather than decorative: the post-command
maintenance policy (strict --readonly, `bd serve`); the read-only
classification, without which `bd list` prunes the journal, since a read-only
classification still opens a writable store; preview commands (--dry-run,
--inspect), which promised not to mutate and are held to it by a
write-refusing open; and the whole `bd events` family, because reading a feed
must never be what prunes it and `bd events prune` has just resolved the bound
by hand. `bd events tail` joins the read-only classification while it is at it
— it is a pure read that had been opening a writable store. Both write
plumbings are covered: the store path and the proxied-server unit-of-work
provider, which commits maintenance in the ephemeral form (no Dolt commit) that
dolt_ignored tables require.
`bd serve` has no command boundary, so it drives the same routine from a ticker
with a clean, self-contained shutdown. It reads the same persisted watermark
every CLI process reads, so a server and the CLIs beside it share one schedule
rather than three.
EXECUTION is its own transaction per batch: `DELETE ... WHERE seq < ? ORDER BY
seq ASC LIMIT 10000`, at most 3 batches per pass, all of it under a 30-second
pass budget and on NO-RETRY transactions. Maintenance is humble by
construction: the retry helper a user's write deserves would spend up to
fifteen seconds of backoff per transaction losing races on work nobody asked
for, and a maintenance transaction that loses a race has nothing to recover —
the prefix delete is idempotent and the next trigger resumes. ORDER BY is not
decoration —
without it a limited delete may remove an arbitrary subset, and a hole above the
floor is silent loss the left-edge truncation check cannot report. Verified
against the real engine that Dolt accepts the ordered, limited single-table
delete. A backlog larger than one pass drains over the next few commands instead
of stalling this one. Every failure is logged and swallowed: maintenance must
never fail a user's command.
THROTTLE is a watermark (ts + head) in local_metadata, the clone-local ignored
plane, read together with the seq counter in ONE query — the not-due answer, the
overwhelmingly common one, costs a single indexed lookup. Due means an hour
elapsed or 5k records advanced; both are constants, because they tune how often
maintenance amortizes rather than promising anything. A stamp far in the FUTURE
(a clock stepped forward and later corrected) also reads as due, or the
interval trigger would never fire again on a workspace that looks healthy. The
watermark is stamped BEFORE the deletes, in a transaction of its OWN, so a
persistently failing prune degrades to no maintenance rather than to a retry on
every command — sharing that transaction with the bound read, as the first cut
did, rolls the stamp back with the failure and reinstates exactly the
per-command penalty the ordering exists to prevent. Racing processes are
idempotent: a prefix delete that already happened is indistinguishable from one
that just did. A workspace with the journal off pays nothing at all.
CONFIG: `events-journal-auto-prune`, default true, BD_EVENTS_JOURNAL_AUTO_PRUNE,
a config.yaml startup key like the other three (YamlOnlyKeys — without that entry
`bd config set` reports success and changes nothing; all four are also added to
the recognized-key set, so the documented opt-out no longer prints an
unrecognized-key warning beside a command that worked). The resolver now reads
the YAML 1.1 boolean spellings, which is a data-deletion fix rather than a
nicety: an unreadable value falls through to the key's DEFAULT, so
`events-journal-auto-prune: no` had read as "keep pruning" — an opt-out that
went on deleting the records it was written to preserve. Its two resolution
arms (no workspace yet, and a resolved workspace) now agree on an unreadable
value instead of splitting false/true. Manual `bd events prune` remains for an
earlier, on-demand cut below the floors; physical disk reclaim still rides
`dolt gc`.
SCOPE: a pass maintains the workspace whose command triggered it. A routed
write (`bd create --repo ../other`) journals into the target, but the trigger
runs against the launcher's own workspace and plumbing — one command must not
open maintenance transactions on every database it wrote to in passing — so a
workspace only ever written remotely relies on commands run in it, or on its
own `bd serve`. Stated in the docs rather than papered over.
Consumer safety is unchanged. ComputeEventsPruneWhere, the truncation contract
and the seq counter are untouched; deletion is still prefix-only and still
bounded by the floors, and a laggard still gets the typed truncation error.
Docs corrected, because four places said the opposite:
- docs/reference/events-journal.md: "manual to prune" in the opening, and
"Pruning is manual. Nothing deletes journal records on your behalf." at the
head of the retention section, which is rewritten around the new default;
the config table gains the key and stops attributing the floors to the CLI
command alone.
- cmd/bd/events.go: `bd events prune` said "Pruning is manual: nothing deletes
journal rows on your behalf"; it now says retention is enforced to the
floors automatically and this command is for an EARLIER, on-demand cut
BELOW the floors — it cannot cut deeper than they allow, so shrinking the
retained window means lowering them. `bd events` gains the same summary.
- CHANGELOG.md: "Pruning is manual — nothing deletes journal records on your
behalf" replaced by the bounded-by-default story, including the volume
trigger and the per-workspace scope.
- internal/config/config.go and internal/storage/issueops/journal_prune.go:
floors are no longer described as belonging to `bd events prune`.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Agent-Signature: claude-code-claude-opus-5[1m]-unknown-reasoning on behalf of julianknutsen
|
||
|
|
98a849fdeb |
docs(events): document the events journal and bd events (bd-opisf)
Adds docs/reference/events-journal.md: what the journal is (a durable, replayable, clone-local record of committed mutations), how to enable it, the record contract and its seven ops, the typed truncation error and how a consumer recovers, manual pruning and the retention floors, the boundaries (per replica, per branch, sync/raw SQL/store-open/compaction non-coverage), and how the dolt_ignored tables sit in Dolt. Every technical claim is derived from the code on this branch — cmd/bd events help text, the issueops journal engine, the migrations, and the config defaults — and states the same contract the CLI help does. It lands in Reference rather than Core Concepts because the page specifies a machine-facing contract rather than teaching the bead/dependency model; it sits beside the JSON output contract and observability, the two other pages an integrator reads. A short orientation section separates the three event-ish systems (script hooks, audit history, the journal) so a reader picks the right one, and reference/advanced.md no longer claims there is no `bd events` command. CHANGELOG records the new commands, the opt-in journal and its three config keys, the events_journal_truncated failure mode, and is_blocked joining the documented /v0 issue schemas. Both the page and migration 0064's down script also record the one destructive outcome that raises no error: dropping and recreating the journal tables, or losing bd_events_seq, restarts the sequence space at 1, so a parked consumer's checkpoint sits above the new head and reads as "caught up" forever. Nothing about the new journal looks pruned, so the typed truncation error cannot report it — the only remedy is to re-baseline, and the only defence is saying so where someone will be standing when it happens. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Agent-Signature: claude-code-claude-opus-5[1m]-unknown-reasoning on behalf of julianknutsen |
||
|
|
f215b312cf |
docs: re-review and bump the three 90-day-stale freshness markers (#5394)
* docs: re-review and bump the three 90-day-stale freshness markers azure-devops.md, json-schema.md, and otel-data-model.md all carried Last reviewed: 2026-05-08 — exactly 91 days old today, so the doc-flags freshness gate went red on every branch at once. Each doc was genuinely re-reviewed against its Freshness source paths on main; drift found and fixed: - azure-devops.md: proxied-server mode note (ado sync/status/projects hard-error there); --parent and --project flags documented; duplicate --issues row dropped; real ado.org error text quoted; ado.pat's yaml-only secret storage noted. Source bug found during review filed separately: the cmd-level config reader cannot see a yaml-only PAT, so the documented Quick Start fails on main (bd-wvf77). - json-schema.md: bd show/close/update --json return top-level ARRAYS (contract-test-pinned); jq recipes fixed accordingly; envelope pagination key documented; show's always-present revision field and import's unchanged counter added; error envelope (optional code/hint, envelope-mode shape, stdout paths, exit 1) corrected; deprecation notice's tty-only condition noted. - otel-data-model.md: hard opt-in via BD_OTEL_ENABLED + standard OTel SDK env vars (BD_OTEL_* legacy); console-only trace export; dolt span attrs (shared doltSpanAttrs, dolt.remote, dolt.force_push, dolt.conflicts, dolt.merge_with_strategy); facade-wave storage role spans; seven new Dolt metrics; AI attrs (bd.ai.duration_ms, 4-attempt retry); appendix re-pinned to current main. scripts/check-doc-freshness.sh passes locally. * docs(otel): complete claim_verify_lost op label enumeration; qualify retry policy as compact-only Review findings on #5394: op also takes guarded-update (claim_verify.go:113) and ready-claim (issues.go:439); the retry-policy sentence describes compact's loop, find_duplicates makes one unretried call. |
||
|
|
80af94c0a7 |
fix(export): promoted no-history wisps survive import round-trip (bd-r9uce) (#5368)
* fix(export): promoted no-history wisps survive import round-trip (bd-r9uce) A promoted no-history wisp is a durable issues-table row that still carried no_history=true (PromoteFromEphemeralInTx cleared only Ephemeral), and both import routes inferred the storage plane from row flags (issueops.IsWisp: Ephemeral || NoHistory). Importing an export stream containing such a row silently re-planed it into the wisps table, dropping its cross-plane relations as cross-bucket dependencies on the way — classic-mode data loss across export->import->export. Belt and braces, per triage + the #5351 review endorsement: 1. Promote clears NoHistory too (and normalizes an explicit ephemeral StorageClass to empty, the NormalizePersistenceMode promotion rule): a promoted wisp is fully durable and the flag has no post-promotion meaning. 2. Import routes the plane by an explicit marker, never by no_history. Export stamps "wisp": true on rows that live in the WISPS table and whose flags alone cannot prove it (the no_history shape; ephemeral rows self-describe), classified by table membership via a new PartitionWispIDs store capability (classic: batched wisps-membership query behind an Unwrap walk through the telemetry/hook decorators; proxied: the plane-pinned GetWispsByIDs read). Both parse loops (bd import in both storage modes, bootstrap/init/auto-import) resolve the plane through one shared helper, applyImportWispPlane, which pins marker-less no_history records to the durable plane via a new non-serialized Issue.WispPlaneOverride consulted by IsWisp. The no_history flag itself is preserved on the row, keeping the export->import->export byte-identity for wild data; the legacy v0.35-v0.37 "wisp"-as-ephemeral alias behavior is unchanged. Tests: the promoted shape now lives IN the proxied round-trip fixture (cross_mode_byte_identical covers it, with relations); a classic-mode round-trip test (fails on base: cross-bucket dependency drop + missing plane marker); a proxied import plane-routing subtest; embeddeddolt pins for promote clearing NoHistory and PartitionWispIDs membership classification. * docs(schema): document the export stream's explicit wisp plane marker (bd-r9uce) * fix(export): fresh wisp_plane marker key — legacy-alias reuse was lossy under rollout skew (bd-r9uce) Adopts lion's #5368 review recommendation: pre-fix v0.38+ binaries' alias branch (hasWisp && !Ephemeral => Ephemeral=true) would import a marked genuine no-history wisp as ephemeral — purge-eligible and excluded from that rig's next export — making the common skew case lossy. A fresh key degrades to flag routing on older readers, data-safe in both directions. Legacy 'wisp' read alias preserved verbatim. * fix(storage): clone WispPlaneOverride in cloneIssueForHook (bd-r9uce) The hook snapshot guard (TestCloneIssueForHookCoversReferenceFields) correctly flagged the new *bool field as uncloned: a hook mutating the snapshot could alias the live issue's plane pin. Deep-copy it and add it to the guard's covered-fields manifest. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FZXjihTayFcngYzo6xPd3p --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
1cf7490c71 |
refactor(telemetry): adopt standard OTel SDK env vars with BD_OTEL_* back-compat (#3859)
* refactor(telemetry): adopt standard OpenTelemetry SDK env vars with explicit BD_OTEL_ENABLED gate
Telemetry configuration moves from BD_OTEL_* data variables to the
standard OTEL_* environment variables. Activation now requires
BD_OTEL_ENABLED=true so a machine-global OTEL_* setting (for some other
instrumented tool) cannot silently turn bd telemetry on.
The resource picks up resource.WithFromEnv() so OTEL_SERVICE_NAME and
OTEL_RESOURCE_ATTRIBUTES take effect — users can override service.name
or stamp deployment.environment without forking.
Backwards compatibility: BD_OTEL_METRICS_URL, BD_OTEL_LOGS_URL, and
BD_OTEL_STDOUT continue to work and activate telemetry on their own.
Each translates to its OTEL_* equivalent unconditionally — a legacy
value wins over a pre-existing OTEL_* value so cross-tool environments
don't redirect bd traffic. Every invocation that sees a BD_OTEL_*
variable logs a one-line deprecation warning to stderr.
OTLP endpoint discovery is delegated to the SDK; the explicit URL
helper in internal/telemetry/otlp.go is removed.
OTEL_SDK_DISABLED=true forces telemetry off even when an opt-in is in
effect, matching SDK conventions.
Tests cover: legacy var translation + override semantics, master-switch
activation, OTEL_*-alone-doesn't-activate, OTEL_SDK_DISABLED override,
WithFromEnv resource attribute merge, deprecation warning emission, and
otlpMetricsEndpointSet detection (specific endpoint, generic endpoint,
negative).
Part of the gastownhall/beads PR #3475 split: isolates the OTEL_* env
var standardisation from the WrapStorage wiring (#3858) and the
bd.prefix attribute work (separate follow-up).
* refactor(cmd/bd): extract command-telemetry wiring into testable helpers
cmd/bd/main.go's PreRunE block contained two pieces of OTel wiring —
telemetry.Init and the bd.command.<name> root span construction — that
silently decay if a future refactor drops them. Same regression class
as the WrapStorage bug (telemetry implemented but never called) —
wiring buried in cobra PreRunE escapes review.
Extract two helpers in cmd/bd/command_telemetry.go:
- commandSpanAttrs(cmdName, version, args): pure function returning
the initial attribute set for the bd.command.<name> span. The
bd.actor attribute is stamped later via SetAttributes once
getActorWithGit() resolves.
- startCommandTelemetry(...): composes Init + Tracer.Start. Single
callable line in main.go.
Three tests cover the commandSpanAttrs output (full attrs and the
empty-args edge case) and the startCommandTelemetry wiring contract:
it must always return a non-nil context and span, since the
bd.command.<name> span is the parent for every storage and AI span
downstream — silently returning nil would break trace nesting across
the whole invocation. Whether the span actually records is governed
by telemetry.Init which is covered in internal/telemetry/telemetry_test.go.
main.go shrinks by 10 lines.
* fixup(cmd/bd): stage command_telemetry.go split omitted from merge commit
The previous merge commit (
|
||
|
|
f4fcc318d2 |
docs: remove stale 8-char prefix limit claim from rename-prefix help (GH#770) (#4813)
Commit
|
||
|
|
3102cbd4d4 |
fix(show): flag comment_count as omitted when comments are not streamed (#4865)
* fix(show): flag comment_count as omitted when comments are not streamed bd show --json's count-only default (be-ijck6q, pinned by TestJSONContract_ShowOmitsOptInPayloadsByDefault) returns comment_count with no comments key unless --include-comments is passed. A caller that checks only `.comments` reads absence as "no comments" — wrong whenever comment_count > 0. A caller that reads comment_count has no signal that the array was left out on purpose rather than omitted by a client bug. Add comments_omitted (bool, omitempty) to IssueDetails, set true only when CommentCount > 0 and Comments was not populated. Leaves the count-only default and its perf rationale untouched — this does not revert cfcc95799/e538f2881, it makes the partial representation self-announcing. Symmetric dependent_count/dependents case is a known, separate gap left for a follow-up (not reported by ga-clgh, out of scope here). Did not bump JSONSchemaVersion / regen the golden corpus: this adds an optional field via omitempty (no existing consumer's parse breaks, no existing corpus fixture has comment_count > 0 for `show`), and corpus-regen needs a live Dolt+Docker this sandbox doesn't have to verify. Deferring that call to whoever can run `make corpus-regen` and review the diff. Fixes ga-clgh. * fix(show): set comments_omitted on the proxied path too The direct path flags comment_count as omitted when comments are not streamed (ga-clgh); the proxied path silently left the flag unset. Mirror the else-branch in proxiedBuildDetails, add proxied-path coverage for present/included/zero cases, and document comments_omitted in the JSON schema reference. No schema version bump: additive optional field already emitted by the direct path. Addresses the 2026-07-23 review on gastownhall/beads#4865. Agent-Signature: claude-sonnet-5-medium on behalf of maphew --------- Co-authored-by: matt wilkie <maphew@gmail.com> |
||
|
|
8c974d8dc4 |
docs: align user docs and CLI reference with the v1.1.0 release (#4874)
* docs: align user docs and CLI reference with the v1.1.0 release The Mintlify site publishes from main, so the docs had drifted ahead of the latest release — documenting features that no released binary ships. - Remove architecture/storage-backends: v1.1.0 supports only Dolt (the SQLite/Postgres/MySQL backends are post-tag work on main). Redirect the old routes to /architecture/dolt and drop the alternate-backend claims from README, core-concepts, architecture index, and FAQ. - Stamp the docs homepage with the release: "These docs are for the 1.1.0 release" plus a link to the v1.1.0 release notes. - Regenerate the CLI reference (docs/cli-reference/, docs/CLI_REFERENCE.md, docs.json nav) from the released v1.1.0 binary via tools/docsmint: removes pages for post-1.1.0 commands (heartbeat, migrate-personal, reclaim, unclaim) and post-1.1.0 flags. - Fix hand-written pages that referenced post-1.1.0 CLI surface, verified against the v1.1.0 binary: bd dep relate/unrelate (not top-level relate/unrelate), bd admin reset (not bd reset), bd hooks list (not status), bd history / bd vc status (not vc log/diff), release the claim via bd assign + bd update (no bd unclaim), drop bd migrate-personal and bd daemon mentions, and reduce the Cursor integration page to the rules file that v1.1.0's bd setup cursor actually installs. - Stop documenting the BD_ALLOW_REMOTE_MIGRATE escape hatch (and the nonexistent-in-1.1.0 bd migrate --force): the remote-migrate gate prints its own operator guidance by design; docs now defer to it. * docs: auto-regenerate CLI reference Applied from the cli-docs-freshness-patch artifact of https://github.com/gastownhall/beads/actions/runs/29613329749 (generated with CI's canonical pinned build). See scripts/check-cli-docs-drift.sh for how drift is attributed. --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> |
||
|
|
0cf3b3ca09 | Revert "Simplify the supported backends: roll back the direct PostgreSQL and MySQL adapters" (#4857) | ||
|
|
98573a4983 |
docs(storage): explain focused Dolt and SQLite support (bd-sadcd)
Document the rollback of direct PostgreSQL and MySQL adapters because supporting additional general-purpose server databases adds operational and maintenance complexity. Our goal is to keep Beads as simple as possible and consume as few resources as possible. Clarify that embedded Dolt, Dolt server, and SQLite remain supported, including Dolt server use of the MySQL wire protocol. Existing PostgreSQL and MySQL databases are not opened or modified. Migration requires an export from a supporting build, reinitialization guided by bd help init-safety, and import; JSONL remains issue-level interchange rather than a complete backup. Refresh generated CLI reference material alongside the storage guidance. Agent-Signature: codex-gpt-5-unknown-reasoning on behalf of Test User |
||
|
|
a530479774 |
test(protocol): pin the §J interchange clauses; fix the bd import header gap (wy-ltox1)
WP2 of the beads protocol conformance corpus (PROPOSAL-beads-protocol-v0 §11).
Seven §J clauses had no test; the keystone one had no test anywhere.
cmd/bd/protocol/interchange_test.go pins:
J6.6 export -> import into an EMPTY store -> export reproduces the stream.
bd-go had same-DB byte-stability and per-field round-trip tests, but
nothing asserted CROSS-store equivalence — the exact blind spot where an
import that drops, defaults, or rewrites a field hides. The fixture
spans P0 (the zero-value priority), every core issue_type, a closed
issue with close_reason, labels, inline comments, both dep orientations,
metadata, assignee, and memories.
J1.2 unknown fields ignored — a record carrying future fields at the top
level, inside a dependency, and inside a comment still imports cleanly.
J1.3 `_schema` header records skipped, on both the file and stdin readers.
J6.1 import never deletes: a partial stream does not remove the ids it omits.
J6.3 a dependency whose target id is absent is skipped AND reported
(skipped_dependencies in `bd import --json`).
J6.5 `bd import -` reads a stream from stdin.
J2.4 zero/unknown timestamps export as the Unix epoch, never year-0001
(which json.Marshal refuses outright, failing the whole export).
J1.3 needed a code fix, not just a test. parseJSONLFile (the bootstrap reader)
skipped the header record, but the `bd import` reader loop — the one `bd import`
and `bd import -` both run through — did not: the header line fell through to
the issue path, unmarshaled into an empty Issue, and aborted the WHOLE import
with "validation failed for issue : title is required". So a canonical export
carrying a provenance header could not be imported at all. Verified against the
new test: it fails with exactly that error without the guard.
Two stale artifacts found during the extraction, fixed here:
- TestCLI_Import_PrefixValidation_E2E asserted that import REJECTS a foreign
id prefix without --skip-prefix-validation, a flag that no longer exists
(every import path now passes SkipPrefixValidation). Being build-tagged out
of CI (cgo && integration), it rotted unnoticed. Rewritten as
TestCLI_Import_ForeignPrefix_E2E, pinning the behavior bd actually has —
which the protocol requires anyway: §J6.6 round-trips through a store with
its own prefix, and §J7.1 makes ids opaque.
- docs/reference/json-schema.md claimed export emits schema_version per line.
It does not (and must not): schema_version belongs to the --json command
envelope; the interchange's version marker is the optional _schema header.
The import --json summary field list was also missing five fields.
Gates: gofmt + go vet clean, go build ./... ok, full cmd/bd/protocol package
green (280s, -count=1).
|
||
|
|
69e33099b8 |
docs: apply PR #4722 review feedback to user docs
- FAQ: fix bulk migration/export commands (tracker pull/push need explicit IDs; bulk lives on sync --pull-only / --pull / --push-only / --push) and correct the cross-project isolation claim (hydration + external targets) - quickstart: replace the broken DOLT.md blob URL with site links - installation: Quick Install Script heading matches its macOS/Linux/FreeBSD body - rewrite 33 stale SCREAMING_CASE link labels to the target pages' titles - add description frontmatter to the 55 authored pages missing it - docs.json: wildcard redirects for the old /next/ and version-prefixed route spaces (1.0.0, 1.0.4, 1.0.5) |
||
|
|
5b660081ed |
docs: fix fact errors found by adversarial review
Every fix verified against the binary or a traced source call path: - How Beads Works: parent-child DOES affect ready work (a blocked parent blocks its children — the blocked-state SQL inherits it), hash-ID inputs are title/description/creator/creation-time plus a collision nonce (live generator is internal/idgen, not the legacy one), and bead gates dropped from the gates bullet (currently unresolvable). - Graph-link type strings are hyphenated (relates-to, replies-to); underscore forms are rejected by the parser. Fixed corpus-wide including the README feature bullet. - upgrading.md: the version-era table's boundaries were refuted by git archaeology (embedded Dolt shipped in v0.60.0 and always used .beads/embeddeddolt/; nothing changed at the claimed v0.63.3 boundary) — replaced with a layout-based identification table. - Phantom .beads/dolt/sql-server.log path in sync-failures and the FAQ; the managed server writes .beads/dolt-server.log. - FAQ: ID growth matches DefaultAdaptiveConfig (min 3, max 8, probability-driven), dependency-type wording no longer claims exactly four types, and unmeasured performance figures are hedged instead of stated as facts. - quickstart: bd migrate --cleanup doesn't exist; init installs hooks by default (no prompt) and has no merge-driver step; ready output quote and bd restore recovery source corrected. - formulas.md: step types map to issue types (task/bug/feature/epic/ chore; anything else falls back to task — human/gate are not step types), and there is no built-in formula search tier. - todo.md: bd update takes --set-labels, not --labels. - graph-links.md: broken #dependencies fragment → #add-dependencies. |
||
|
|
cf2711f13e |
docs(nav): nest the CLI Reference under Reference
The 113-page generated CLI Reference moves from a top-level nav group to a nested group inside Reference — Mintlify renders nested groups collapsed by default, so the command list no longer dominates the sidebar. The docsmint nav splice is depth-agnostic (indentation is derived from the pages key's line), verified by regenerating and by re-pointing its test fixture at the nested shape. Reference overview and the beads-docs skill IA note updated. |
||
|
|
5c2fe0d496 |
docs: loss-check merge legacy docs/ originals into the site pages
Sixteen same-ground pairs reconciled: the ported website page won and every
fact living only in the older docs/ original was merged in before deleting
it (INSTALLING, SETUP, ARCHITECTURE, CONFIG, GIT_INTEGRATION, ADVANCED,
TROUBLESHOOTING, FAQ, MOLECULES, ROUTING, MULTI_REPO_AGENTS, and the five
agent-integration guides). The two divergent FAQs are now one canonical
FAQ with the comparison-first framing leading and pre-1.0 version gating
removed.
The routing pair inverted: the ported page described a routing system that
does not exist (bd routes, bd hydrate, .beads/routes.jsonl); the page was
rebuilt from the accurate sources (ROUTING.md + MULTI_REPO_AGENTS.md)
against the real surface (routing.mode config, role detection,
repos.primary/additional hydration, --repo override).
Every claim was verified against the Go source or the generated CLI
reference. Phantom surfaces removed across the corpus: bd pin/hook/pour/
wisp/routes/hydrate/reserve/lock/merge/stats, bd hooks status, bd vc
conflicts/log, bd dolt set shared-server, fictional beads_* MCP tool names
(real names taken from beads-mcp server.py), import.orphan_handling and
export.error_policy config keys, database.wal_mode/cache_size, stale
.beads/dolt/sql-server.log paths, and SQLite-era file paths. Notable
corrections: min_hash_length default is 3, .beads/metadata.json is
git-tracked, tombstone status is removed, wisps live in the main database
under an ephemeral flag.
Freshness markers moved with their content (configuration, ide-setup) and
the freshness gate + DOC_INVENTORY ledger now point at the new locations.
Load-bearing anchors preserved: #branchless-workflows-jujutsu--jj,
#circuit-breaker-server-appears-down-failing-fast, #extensible-database,
#multi-repo-hydration. Pointer stubs remain at docs/{SETUP,INSTALLING,
GIT_INTEGRATION}.md because released binaries print those paths
(allowlisted in the docsync guard).
Gates: go test ./test/docsync PASS; generate-cli-docs.sh --check PASS;
check-doc-freshness.sh PASS; mint broken-links: no broken links; mint dev
serves all sections (11 routes spot-checked at 200).
|
||
|
|
54a7547b0d |
docs: canonical concept page, section overviews, and concept diagrams
- Rewrite docs/core-concepts/index.md as 'How Beads Works' — the one canonical concept page (modeled on Gas City's how-gas-city-works): problem-first framing, the bead -> dependencies -> ready-work loop, hash IDs, the formula -> proto -> molecule/wisp pipeline, gates, Dolt sync, federation, and storage modes, led by four mermaid diagrams. Other pages link here instead of re-deriving the model. - Fact fixes caught while writing (verified against source/live help): hash IDs are content-derived SHA-256 (internal/types/id_generator.go), not random; bd pour/bd wisp/bd pin/bd hook are not commands (the real surface is bd mol pour, bd mol wisp, bd assign, bd update --claim); wisps are not stored in .beads-wisp/; cross-repo dependency syntax is external:<project>:<capability> (cmd/bd/dep.go); recovery index claimed SQLite errors in a Dolt product. - Section overview pages: rewrite workflows/index, extend recovery, multi-agent, and integrations indexes to list every child with one-line summaries; add reference/index and put it in the nav. - New concept diagrams: sequential-vs-hash ID merge collision (core-concepts/hash-ids) and embedded-vs-server mode (architecture/dolt). - website/README.md: real build/verify instructions and parallel-run status for the Docusaurus site. - Repoint example READMEs and page labels at the real quickstart page. |
||
|
|
a86a2c5dfc |
docs: port user docs into the Mintlify site tree under docs/
Port the website's current (non-versioned) authored pages into docs/ as
Mintlify pages: frontmatter title as H1 (body H1s removed), Docusaurus
admonitions converted to Mintlify components, root-relative extensionless
links, mermaid fences kept as-is. website/docs/** is untouched and keeps
serving the Docusaurus site until cutover.
git-mv the user-facing docs/ originals with no website twin into the new
IA (core-concepts/, architecture/, workflows/, recovery/, multi-agent/,
integrations/, reference/), converted to Mintlify form. The six files
previously mirrored by sync-website-docs.sh now live once in the site.
ERROR_HANDLING.md and CONTRIBUTOR_NAMESPACE_ISOLATION.md move to engdocs/
(contributor pattern guide and design doc per content review).
Pointer stubs remain at docs/{DOLT,STORAGE-BACKENDS,PLUGIN,RECOVERY,
JSON_SCHEMA}.md because released bd binaries print those paths; QUICKSTART
and DOLT-BACKEND stay as existing pointer stubs.
Corrected while porting: the intro's storage diagram claimed data lives at
.beads/dolt/; embedded mode (the default) uses .beads/embeddeddolt/
(internal/storage/embeddeddolt/store.go), server mode uses .beads/dolt/.
|