~bigbes/sr-ht-dolt · issues

e318p993oaoslunb95jj5c67d6flrq55 · 61 rows

idcontent_hashtitledescriptiondesignacceptance_criterianotesstatuspriorityissue_typeassigneeestimated_minutescreated_atcreated_byownerupdated_atclosed_atclosed_by_sessionexternal_refspec_idcompaction_levelcompacted_atcompacted_at_commitoriginal_sizesenderephemeralwisp_typepinnedis_templatemol_typework_typesource_systemmetadatasource_repoclose_reasonevent_kindactortargetpayloadawait_typeawait_idtimeout_nswaitershook_beadrole_beadagent_statelast_activityrole_typerigdue_atdefer_untilno_historystarted_atis_blocked
sr-ht-dolt-02f83853bcb003431d4c7f647e63fd86785fd1b7f51034bbcf7d19ad49e64deac3cmemory view: render markdown and cross-database [[wikilinks]]Memory bodies are markdown (bold, code spans, fenced blocks, lists) but the view prints them as pre-wrap paragraphs. Render them as markdown, and resolve [[slug]] references — including to memories that live in another tracker — through the same cross-database index the issue-id links use.closed2featureEugene BlikhNULL2026-08-15T09:45:53ZEugene Blikhbigbes@gmail.com2026-08-15T10:10:03Z2026-08-15T10:10:03ZNULL0NULLNULLNULL000�{}markdown rendering + cross-database [[slug]] and issue-id links on the memory view; branch feat/memory-markdown, not yet committed0NULLNULLNULL02026-08-15T09:46:14Z0
sr-ht-dolt-03v67d1a84e865ba3ac15599e384da2bdc3a52fe17c1f0c68f8d8ff11de5f0613e9Deeper rename: repo/repository -> database across the serviceThe token grant scope is now dolt.sr.ht/DATABASES (sr-ht-dolt-xic), and the user-facing surface has always said 'database' — the GraphQL databases connection, the web pages, the docs. The layers underneath still say repo: ~1300 identifier occurrences across Go (RepoDiskPath, GetRepoByOwnerAndName, ListReposForViewer, ReposRoot, ...) plus the schema itself (CREATE TABLE repository, access.repo_id, uq_access_user_id_repo_id in schema.sql / migrations/0001_initial.sql). Doing it properly means a data migration on top of the Go rename, so it was deliberately left out of the scope-name fix rather than half-done. Decide whether it is worth it at all: the split is defensible as-is (a Dolt database IS a repository at the storage layer, which is what remotesapi talks to), and the cost is a migration on live data.If taken: rename Go identifiers first in one mechanical pass, then the columns behind a migration, keeping the SQL rename last so a rollback is a schema-only revert.open3taskNULLNULL2026-08-18T09:20:59ZEugene Blikhbigbes@gmail.com2026-08-18T09:20:59ZNULLNULL0NULLNULLNULL000�{}0NULLNULLNULL0NULL0
sr-ht-dolt-0df0c716954c4783ebf0507ecd5bb1c7602f17a83be2759160610f250d38002bcfbA stored "NULL" string is indistinguishable from a real NULLbrowse renders a real NULL cell as the literal string "NULL" (placeholderNull), so a row that genuinely stores the text NULL reads identically. beads.cell() then flattens both to the empty string. Harmless on a rendered page, misleading through the MCP read_rows tool, where an agent gets strings and no schema hint (7a77409 states it in the tool description rather than papering over it).Options, cheapest first: keep the placeholder for the HTML pages and give RowPage a parallel per-cell null mask that read_rows exposes; or render JSON null through the tool path. Whatever is chosen, the beads projection must keep treating a real NULL as empty — that behaviour is depended on.closed3bugNULLNULL2026-08-13T05:46:26ZEugene Blikhbigbes@gmail.com2026-08-13T07:11:43Z2026-08-13T07:11:43ZNULL0NULLNULLNULL000�{}browse half landed in 5b00a51: RowPage.Nulls parallel to Rows, filled from the td.IsNull the renderer already called (no extra tuple read). Rendered strings unchanged, so every existing consumer keeps reading what it read. Mutation-checked by making the mask believe the rendered string. Adoption by mcpsrv and beads is sr-ht-dolt-3s9.0NULLNULLNULL02026-08-13T07:06:40Z0
sr-ht-dolt-0qffc02bbd7697c59c02d006fb5e3bf2c052d971c36d809bea95e2ef0a159889b16MCP surface: a read-only agent door to hosted databasesServe /mcp on the web listener so an agent reads a hosted Dolt database — and the beads tracker inside it — by calling tools instead of scraping the board HTML. Read-only by construction: no SQL tool (the pure-Go build stands on never starting the engine), no writes, no second reading of any schema. Spec: docs/DESIGN.mcp.md.docs/DESIGN.mcp.md is normative. Donor is sourcehut-coverage/mcpsrv (modelcontextprotocol/go-sdk v1.6.1): stateless streamable HTTP, Host allowlist replacing the SDK DNS-rebinding guard, consumer-side seams in ports.go, in-process client over fakes in tests.closed1epicNULLNULL2026-08-12T20:10:04ZEugene Blikhbigbes@gmail.com2026-08-13T07:07:08Z2026-08-13T07:07:08ZNULL0NULLNULLNULL000�{}All eight tasks landed: the beads/ extraction, the bearer plane, the mcpsrv skeleton, the generic tools, the beads tools, the daemon wiring, list_memories and ready_work. docs/DESIGN.mcp.md is implemented end to end.0NULLNULLNULL0NULL0
sr-ht-dolt-0qf.1573249e114393372c5f26a2572baf8fa924e214e471ed31e1d75e9c8245b53d6Extract the beads projection into a root beads/ packageweb/beads.go holds the one reading of the beads schema (fingerprint, lane bucketing, ready rule, status categories, dependency walk, event humanizer, filter model, row helpers) where a second consumer cannot reach it. Move it to a root beads/ package depending on browse/ and the stdlib only; web/beads.go and web/milestones.go keep their View implementations. Prerequisite of the MCP beads tools, of the cross-database ready page and of cross-database links.docs/DESIGN.mcp.md §8. Pure move plus export renames — web/beads_test.go and web/milestones_test.go move with it. A behaviour change smuggled into this commit makes the refactor unreviewable.go test ./... green with the suites relocated; no behaviour diff in the rendered board (same fixtures, same output); beads/ imports neither net/http nor core.closed1taskNULLNULL2026-08-12T20:11:37ZEugene Blikhbigbes@gmail.com2026-08-13T06:05:35Z2026-08-13T06:05:35ZNULL0NULLNULLNULL000�{}Landed in f9c82b0 (pure move: 77 PASS lines preserved, the sorted set of test names byte-identical to the baseline) plus 7d799ed, which dropped the Bead/Beads prefix from the moved types before any consumer existed.0NULLNULLNULL02026-08-12T20:15:38Z0
sr-ht-dolt-0qf.28e6146eadd1e58f2b423fb9dd6cd47903d1e79ea7e967d24566aea9812fb090cauthn: the tokens.sr.ht bearer plane and core.GrantRead/mcp is bearer-only, and this service has no bearer plane today: cookie, meta PAT over HTTP Basic, and the dolt keypair JWT on gRPC. Add ParseBearer plus a plane over sr-ht-ecore/bearer (working tokens issued by tokens.sr.ht, ClientID tokens.sr.ht, gated on the new core.GrantRead = "dolt:read") and a meta-PAT arm over the existing decode path, gated by authn.TokenGrantsAllow at core.AccessRO. A token that fails verification is a refusal, never a downgrade to anonymous.docs/DESIGN.mcp.md §4. sr-ht-ecore/bearer + /grants are already reachable (sr-ht-ecore is a direct dependency); cov.sr.ht authn/instance.go is the donor, including the consumer-side InstanceValidator seam. [tokens.sr.ht]origin absent means no such daemon: working tokens refused, meta PATs and anonymity still work.Table-driven classification tests with no network: working token with/without dolt:read, meta PAT, garbage, absent header (anonymous), expired.closed1taskNULLNULL2026-08-12T20:11:44ZEugene Blikhbigbes@gmail.com2026-08-12T20:27:26Z2026-08-12T20:27:26ZNULL0NULLNULLNULL000�{}Landed in 6288fc1: ParseBearer, the InstanceValidator seam over sr-ht-ecore/bearer, BearerCaller with Authorize, ResolveBearer branching on ClientID, core.GrantRead. Plus 0f6d50a (gofmt drift in authn tests, pre-existing at 587483e).0NULLNULLNULL02026-08-12T20:17:10Z0
sr-ht-dolt-0qf.3700764cff30a4c71a08b87eecb428a469a1701bb257a567e2e8b55e8e1762223mcpsrv: skeleton, Host allowlist, grant gate, list_databasesNew mcpsrv/ package: Server + New over modelcontextprotocol/go-sdk, stateless streamable HTTP, the Host allowlist that replaces the SDK DNS-rebinding guard, the read-grant gate in front of the handler, consumer-side seams in ports.go, and one tool (list_databases) to prove the surface end to end.docs/DESIGN.mcp.md §5-§7. Stateless is an authentication decision: in stateful mode the principal of the initialize request answers every later call. Port the donor identity matrix test and re-run it against our pinned SDK rather than citing the donor.Identity matrix test (no credential / stranger token / owner token on one session) asserts the answer follows the call; Host allowlist test (instance host passes, rebinding host 403 before the SDK); grant gate test.closed1taskNULLNULL2026-08-12T20:11:44ZEugene Blikhbigbes@gmail.com2026-08-13T05:24:51Z2026-08-13T05:24:51ZNULL0NULLNULLNULL000�{}Landed in dcdc979: stateless streamable transport, Host allowlist replacing the SDK rebinding guard, bearer middleware with the 401/403/503 split, grant gate on core.GrantRead, seams in ports.go, list_databases. 59 tests; the identity matrix was verified to FAIL under stateless=false before being reverted.0NULLNULLNULL02026-08-13T05:01:29Z0
sr-ht-dolt-0qf.4bd8dd472bd71f282d5eaea3072a8e8cd448fb2c7d330444408baed331e9121f6mcpsrv: generic tools over the browse seamlist_branches, list_tables, read_rows, get_commit_log, get_commit_diff — the browse surface as tools, addressing a database as {owner, name} and defaulting ref to the repository default branch. Caps: read_rows limit<=500 (default 100), get_commit_log limit<=100 (default 25); a truncated result says so and carries the true total.docs/DESIGN.mcp.md §9.1, §9.3. Visibility is core.Allowed/OpBrowse with NotFoundForPrivate masking — never a distinguishable forbidden.Visibility matrix per tool over PUBLIC/UNLISTED/PRIVATE x anonymous/stranger/grantee/owner; cap tests.closed2taskNULLNULL2026-08-12T20:11:45ZEugene Blikhbigbes@gmail.com2026-08-13T05:46:43Z2026-08-13T05:46:43ZNULL0NULLNULLNULL000�{}Landed in 7a77409: list_branches, list_tables, read_rows, get_commit_log, get_commit_diff in mcpsrv/browse.go. Caps clamp and say so (limit echoes what was applied); a masked database and a missing one share a byte-identical sentence; a bad ref/table is a plain answer, not the mask. 176 tests, cap test mutation-checked.0NULLNULLNULL02026-08-13T05:27:34Z0
sr-ht-dolt-0qf.54fcc9363da648c65592a42ad2216428ffa16a7f84c3e48e5c568583cfa3774a3mcpsrv: beads tools over the shared projectionlist_issues, get_issue, list_milestones, list_memories over the extracted beads/ package. Lists carry identity and metadata and no long-text bodies; get_issue carries the bodies, both dependency directions, the transitive trees, subtasks with the rollup for an epic, comments and the merged history. The tool list is static per server, so a database without the fingerprint is refused per call, naming the generic tools as the way to read it anyway.docs/DESIGN.mcp.md §9.2. list_memories reports the revision each memory was last written at, sharing the walk with the Memory view.A non-beads database answers the beads tools with the explanatory refusal, not an error; list_issues carries no description/design/notes field at all.closed2taskNULLNULL2026-08-12T20:11:46ZEugene Blikhbigbes@gmail.com2026-08-13T06:05:35Z2026-08-13T06:05:35ZNULL0NULLNULLNULL000�{}Landed in e4bc922: list_issues, get_issue, list_milestones over beads.Build/BuildMilestones. The no-bodies rule is structural — the card JSON type has no field a body could arrive in, mutation-checked by adding an empty description field and watching the guard fire. A non-beads database gets an explanatory refusal distinguishable from both the mask and an internal error. 270 tests.0NULLNULLNULL02026-08-13T05:47:34Z0
sr-ht-dolt-0qf.660d5c474af1047fa392cd9d63c9cb26b0accfdf049c3f41f371722ea531999d1Wire /mcp into the daemon, config and READMERegister the MCP handler in cmd/doltsrht/main.go with r.Handle("/mcp", …) before web.Register and outside its CSRF group (bearer surface, no Origin, no cookie), document [tokens.sr.ht]origin in config.example.ini, and describe the surface in README.md.docs/DESIGN.mcp.md §3, §10. Handle and not Mount: Mount rewrites the path to the empty remainder and claims /mcp/*. No mcp-enabled switch — a surface that is off in production is one nobody tests.An httptest boot proves /mcp answers a tools/list and that the CSRF guard does not touch it, while a browser POST to a web form still requires same origin.Also amend authn/ctx.go's package doc: it still says 'the three authentication flows' and does not mention the bearer plane added in 6288fc1. The wiring commit is where that sentence becomes wrong in a way a reader can see.closed2taskNULLNULL2026-08-12T20:11:47ZEugene Blikhbigbes@gmail.com2026-08-13T06:25:23Z2026-08-13T06:25:23ZNULL0NULLNULLNULL000�{}Landed in 57adcc0: /mcp mounted on the web listener before web.Register and outside its CSRF group, bearer validator from [tokens.sr.ht] (absent = nil validator, daemon boots, working tokens refused), config.example.ini and README documented, authn/ctx.go now names four flows. Measured: anonymous tools/list POST to /mcp = 200 even with a foreign Origin; a cross-origin POST to /create = 403.0NULLNULLNULL02026-08-13T06:07:15Z0
sr-ht-dolt-0qf.746b9183986187b6c28b54f588ebbf6016eb24826ef63e641fb59a008be493eddmcpsrv: ready_work across every visible beads databaseready_work with no database named answers the ready set across every beads database the caller may see, sharing the /ready aggregator and its cache; the page and the tool differ in rendering only.docs/DESIGN.mcp.md §9.2 and docs/DESIGN.views.md ch.4 — one implementation, two surfaces.The tool and the page answer the same set for the same caller.closed3taskNULLNULL2026-08-12T20:11:52ZEugene Blikhbigbes@gmail.com2026-08-13T06:44:15Z2026-08-13T06:44:15ZNULL0NULLNULLNULL000�{}Landed in 4038ec2: ready_work with both arms over the shared beads.ReadyCache (one per server, built in New). The arms are proved to agree by decoding both answers and comparing the whole group struct. Two mutation checks: a per-call cache reddens the head-hash-gate test with counted reads, and disabling core.Allowed reddens the access-rule test.0NULLNULLNULL02026-08-13T06:27:32Z0
sr-ht-dolt-0qf.81c93d2f307eb4417b5d5370df5995025d716ffb0161f23f679c44519dbb23cdemcpsrv: list_memories over the shared memory projectionThe MCP beads tools ship without list_memories because the memory projection and its revision walk are landing in beads/ under sr-ht-dolt-b08 at the same time. Add the tool once that API exists: slug, text, and the revision each memory was last written at, with an optional q substring filter — the same reading the Memory view renders, not a second one.docs/DESIGN.mcp.md §9.2. Share beads/ with the web view; the walk is bounded there and this surface inherits the bound rather than restating it.closed2taskNULLNULL2026-08-13T05:46:27ZEugene Blikhbigbes@gmail.com2026-08-13T06:25:30Z2026-08-13T06:25:30ZNULL0NULLNULLNULL000�{}Landed in dba72f5: list_memories over beads.BuildMemories. An unresolved revision is JSON null beside a null age_days, never a zero-valued object (mutation-checked); staleness carried three ways so the threshold is visible. 302 tests.0NULLNULLNULL02026-08-13T06:07:21Z0
sr-ht-dolt-0qf.9de3c132a24997feef8b797370c5c91f3fa20b518a05f408efaa08eb32e7ddc34mcpsrv: carry the per-table clip list on list_issuesThe web board now names every clipped table it draws from and what each clip costs (beads.Data.Clipped, commit 96b5e53). mcpsrv list_issues still reports only data.Truncated as table_truncated, so an MCP client sees nothing about a clipped labels or custom_statuses read — the same blind spot the board just lost. Data.Clipped is already there to be carried.closed3taskNULLNULL2026-08-13T08:12:18ZEugene Blikhbigbes@gmail.com2026-08-13T08:22:57Z2026-08-13T08:22:57ZNULL0NULLNULLNULL000�{}Landed in 07c2a63: list_issues carries clipped[] with table/shown/total/effect, empty list rather than null on a complete read. The agent added a dedicated fixture instead of growing the shared bulk one (whose exact counts other tests assert), which also isolates the case that matters: a labels-only clip fires 'clipped' while table_truncated stays false.0NULLNULLNULL02026-08-13T08:14:38Z0
sr-ht-dolt-16t88a2fd7a5715be57384920506c31f7cd305072679ab394336b15dd060c0a7610Show the original object on the issue detail pageThe issue detail pane (`/view/beads?issue=<id>`) renders a curated subset of an issue: the fields the card model carries, the dependency trees, and the Comments / History tabs. Everything the view does not model — columns bd added that BeadIssue has no field for, custom metadata, the exact stored strings behind the humanized events — is invisible without leaving for the generic table browser and hunting for the row by hand. Show the original object on the detail page: the raw stored row(s) behind the issue, as read from the tables, so the rendering can be checked against what is actually in the database. - Carry the raw data on BeadsData in detail/epic mode: the `issues` row as an ordered column → value list (column order as read, NULLs kept distinct from empty strings — `cell` currently flattens the browse NULL placeholder to "", so read the row directly rather than through it), plus the matching rows from the satellite tables the pane already reads (labels, dependencies, comments, events) when they add anything. - Render it as a third tab in the existing activity switch next to Comments / History — the switch is a pure-CSS radio group, so a third radio + label + panel needs no JS — or as a `<details>` block under the field table if the tab strip gets crowded. Keep it collapsed by default: it is a debugging surface, not the main reading. - Present it as a key/value table plus the row as JSON, both escaped; values can be long free text (descriptions, design notes) and must wrap rather than blow the layout out horizontally. - Reuse the read that already happens in Build — the detail pane loads these tables anyway, so this must not cost another Rows call. - Tests in web/beads_test.go: raw block present in detail mode and absent in board mode, a column with no BeadIssue field still shows up, NULL renders distinctly from "". On /view/beads?issue=<id> the raw stored row behind the issue is visible without leaving for the table browser: every column of the issues row (including ones the view models no field for) with NULL distinguishable from an empty string, collapsed by default, long values wrapping instead of widening the page.closed2featureNULLNULL2026-08-10T11:13:36ZEugene Blikhbigbes@gmail.com2026-08-13T08:27:44Z2026-08-13T08:27:44ZNULL0NULLNULLNULL000�{}Landed in 60fdb6d: beads.RawTable/RawRow/RawCell carried in detail and epic mode, rendered as a collapsed <details> section. The three states (absent / stored "NULL" / empty) are three distinct renderings, and rowCells.isNull became the single site deciding whether a cell holds a value, so the raw view cannot drift from the fields. Linkification deliberately NOT applied — a rewritten value is not the stored value — and a test proves that is a choice, not a broken linkifier.0NULLNULLNULL02026-08-13T08:14:30Z0
sr-ht-dolt-1nta4409772c5498400928984a1730defd5367538dd81902d86b14fe262a05ff0e8Replace hand-rolled meta-PAT cache in authn/token.go with sr-ht-ecore/metapatWhat is wrong: `sourcehut-dolt/authn/token.go` (`ResolveBasic`, `tokenCacheMu`, `tokenCache`, `cacheGet`, `cachePut`, `tokenCacheTTL`, package-level `nowFn`) and `authn/bearer.go::ResolveBearer`/`::resolveMetaPAT` hand-roll a meta-PAT resolution plane instead of using `sr-ht-ecore/metapat`, even though dolt already imports ecore's `bearer`, `grants`, `login`, `csrf`, and `internalauth`. Both dolt's cache and metapat's cache on sha512 of the presented token for 60s and cache successes only, but dolt's cache is a package-level global with no size bound, where metapat's is per-Validator, has an injectable `Now`, and drops the map at `maxCacheEntries = 4096`. dolt does not route through `metapat.PlaneOf` and its `RepoScope = "dolt.sr.ht/repos"` check is inline rather than through `metapat.Allows`, so dolt does not inherit metapat's centrally-defined rules: "no grants ⇒ universal", "no BearerToken ⇒ pass", the ordering guarantee that local checks refuse before any network call, the sentinel set, and the `ErrRevoked → 401 not 403` rule. Why it matters: an unbounded package-level global map in a long-running process is a slow leak (mitigated somewhat because `cachePut` only runs on successful resolves, so growth is bounded by distinct valid tokens rather than attacker input) — but the larger and more durable cost is duplicated authorization policy on the auth path, which will drift from the canonical implementation over time. Sibling that already does it correctly: `sr-ht-ecore/metapat`, which exists precisely so five services do not each write this logic separately. dolt's credential does arrive differently — as an HTTP Basic password (dolt's remote protocol) rather than a bearer header — but that is just header parsing that can sit on top of `metapat.Resolve`. Fix: reduce `authn/token.go` to header parsing + `metapat.Resolve` + `metapat.Allows`, behind dolt's existing `MetaValidator`-shaped port; delete the global cache. The sentinel-error mapping (e.g. revoked → 401) is the only genuinely new work. Status: reported, not independently re-verified against `authn/token.go` in this register — found independently by two separate analysis passes (03-auth D4, 04-federation #4) plus a third pass (05-caching D14) that found the same unbounded cache from the caching angle. Reproduce/re-read `authn/token.go` before treating the size-bound claim as certain. Audit: D11 (cross-repo audit, 2026-08-17) open2taskNULLNULL2026-08-17T20:39:04ZEugene Blikhbigbes@gmail.com2026-08-17T20:39:04ZNULLNULL0NULLNULLNULL000�{}0NULLNULLNULL0NULL0
sr-ht-dolt-24m80da86c30c9b954f83f50022382fb01931c435e6b70be43b296049b875bf0b01Existing companion databases still carry an initial commitDatabases provisioned before sr-ht-dolt-dwz have a WriteEmptyRepo 'Initialize data repository' commit on disk, so their owners still need one 'bd dolt push --force' / 'dolt push -f'. Decide whether to leave them (documented one-time force), offer an operator command in doltsrht-migrate that re-inits a store which holds nothing but that single empty commit, or expose it as an owner-facing settings action. Re-initing must evict the served handle (storage.Cache.Evict) and must refuse any store that has real history.open3taskNULLNULL2026-08-15T09:03:00ZEugene Blikhbigbes@gmail.com2026-08-15T09:03:00ZNULLNULL0NULLNULLNULL000�{}0NULLNULLNULL0NULL0
sr-ht-dolt-2woa033adbfe134dfe67605d81f37366dc2f9160455e78098614617bb45320d2d91The detail and milestone projections drop the truncation signalbeads.Build's detail branch never sets Data.Truncated and BuildMilestones discards the table totals with _, so on a tracker larger than beads.Max (2000 rows) an id in the tail reads as simply absent and a milestone rollup is computed over the first 2000 rows — with nothing saying so. The board path does report its clip; these two do not. Found while building the MCP get_issue and list_milestones tools (e4bc922), which cannot detect it without a second row read and state the limitation in a package comment instead.Return the totals from those two entry points the way the board path already does, and let both surfaces render them. The alternative — each surface re-reading to check — is the second reading of the schema the beads package exists to prevent.A fixture tracker past the cap: get_issue on a tail id says the read was clipped rather than claiming the issue does not exist; a milestone rollup over a clipped read is marked.closed2bugNULLNULL2026-08-13T06:05:05ZEugene Blikhbigbes@gmail.com2026-08-13T07:17:39Z2026-08-13T07:17:39ZNULL0NULLNULLNULL000�{}Landed in ebbd30f: Truncated/ShownOf are now set in every mode (no new fields, no changed meanings), plus IssuesClipped/Missing/MissingBeyondCap so a surface can tell 'no such issue' from 'not in the rows I read'. Fixture clips through a seam that honours limit rather than lowering Max.0NULLNULLNULL02026-08-13T07:08:28Z0
sr-ht-dolt-2zk77fe46c6625a140ae7f2a43069c671d945098761326c993f9eaae87276edd372Add api/layering_test.go to machine-check the core<-db/authn<-service<-api/web invariantWhat is wrong: dolt declares the `core ← db/authn ← service ← api/web` layering invariant in prose only (per the family-wide convention documented in e.g. `sourcehut-tokens/CLAUDE.md`, `sourcehut-coverage/SPEC.md` invariant 7) and enforces it by code review alone — there is no machine check. Why it matters: the invariant is what makes `service/` the single place a security check can be located. A prose invariant nothing checks is exactly the kind of invariant that quietly stops holding as the codebase changes. Sibling that already does it correctly: `sourcehut-bench/api/layering_test.go` and the artifacts equivalent machine-check the invariant, including guards against three known bypass patterns ("close the three ways round the layering guard"). Fix: copy `api/layering_test.go` into dolt, adjusting package names and import paths as needed for dolt's package layout. Status: reported, not independently re-verified by direct code reading for this register entry. Audit: D18 (cross-repo audit, 2026-08-17) open2taskNULLNULL2026-08-17T20:39:18ZEugene Blikhbigbes@gmail.com2026-08-17T20:39:18ZNULLNULL0NULLNULLNULL000�{}0NULLNULLNULL0NULL0
sr-ht-dolt-3r61b7565cdfd5db4add5867091cfc24a4aa17cd5655199577d4cd1fcc011a4e0c3Derive api-meta.json scope list from a single core.Grants arrayWhat is wrong: dolt's `api-meta.json` scope list and its locally checked grant string are declared and maintained separately, instead of being derived from one source of truth. Why it matters: when the two lists drift, a rename silently invalidates every minted token and nothing on the service side notices — this is the exact mechanism behind D07 (tokens' SPEC minting `cover:` grants no service honors) and D16 (the mint vocabulary having no cross-check against services), applied within a single service instead of across services. Sibling that already does it correctly: `sourcehut-artifacts/core/grants.go` + `core/grants_test.go` (and bench's equivalent) derive both the scope list and local grant validation from one `core.Grants` array and pin the agreement with a test. Fix: adopt the single-array + agreement-test pattern in dolt: define one `core.Grants` array dolt's `api-meta.json` scope list and local validation both read from, and add a test asserting they agree. Status: reported, not independently re-verified by direct code reading for this register entry. Audit: D17 (cross-repo audit, 2026-08-17) open2taskNULLNULL2026-08-17T20:39:11ZEugene Blikhbigbes@gmail.com2026-08-17T20:39:11ZNULLNULL0NULLNULLNULL000�{}0NULLNULLNULL0NULL0
sr-ht-dolt-3s9dce98d223440919c25ba0e8e76711bd6f573a359b6c581ba922a0b73f56fd3fcAdopt browse's NULL mask in the readersOnce browse can say which cells are actually NULL (sr-ht-dolt-0df), the readers should use it: mcpsrv read_rows should render a real NULL as JSON null instead of the string "NULL" and drop the warning from its tool description, and beads.cell() should keep treating a real NULL as empty while no longer flattening a stored "NULL" string to empty. The browse half is deliberately separate so the placeholder rendering the HTML pages depend on does not change under them.closed3taskNULLNULL2026-08-13T07:06:41ZEugene Blikhbigbes@gmail.com2026-08-13T07:36:37Z2026-08-13T07:36:37ZNULL0NULLNULLNULL000�{}Landed in 6307350 (read_rows cells are [][]*string — a real NULL is JSON null on the wire, asserted on the marshalled payload; the description's warning removed) and c82f106 (beads rowCells binds a row to its mask, so cell() reads the mask and ~100 call sites did not move). A hand-built page with no mask keeps the older reading, documented and pinned rather than left implicit.0NULLNULLNULL02026-08-13T07:18:50Z0
sr-ht-dolt-44nb50bb951638f211f7c763f1bd998752099e314d771c1f93716eedd99ce9cb4d0Beads views, round two: stream, memory, freshness, cross-databaseSix changes to the read-only beads surface: a one-column stream layout beside the parade board, a view for the memories bd remember writes, a freshness line in the header, a cross-database ready page, cross-database issue links, and copy-ready bd commands on the detail pane. Spec: docs/DESIGN.views.md.docs/DESIGN.views.md is normative. Everything stays read-only and stays inside the todo.sr.ht visual idiom the board already adopts.closed2epicNULLNULL2026-08-12T20:10:11ZEugene Blikhbigbes@gmail.com2026-08-13T07:07:08Z2026-08-13T07:07:08ZNULL0NULLNULLNULL000�{}All six chapters of docs/DESIGN.views.md landed: the stream layout, the memory view with its revision walk, the freshness line, the /ready page, cross-database links and the copy-ready bd commands.0NULLNULLNULL0NULL0
sr-ht-dolt-44n.1c75e486b670035125752508f498c905d88782b651d831cb7a753cb4b3a6ade9aBeads board: the one-column stream layout?layout=stream renders the same filtered cards top to bottom under section headers in parade order (Rolling, Lined Up, Stalled, Past Stand) with a Board | Stream toggle in the filter bar. Each section sorts to its own question: Rolling by started_at desc, Lined Up ready-first, Stalled by fewest blockers, Past Stand by closed_at desc. Past Stand is collapsed in a <details> — no JavaScript.docs/DESIGN.views.md ch.1. A layout of the Beads view and not a fifth tab: filters, the ready toggle and ?issue= links are unchanged. Needs the withQuery template func so the toggle preserves the current query.Section counts equal lane counts for the same filters; each section order asserted; an unknown layout value falls back to the board.closed2taskNULLNULL2026-08-12T20:11:47ZEugene Blikhbigbes@gmail.com2026-08-13T05:17:21Z2026-08-13T05:17:21ZNULL0NULLNULLNULL000�{}Landed in fe616c8: ?layout=stream with per-section sorting, Past Stand collapsed in <details>, sticky section heads, withQuery template func. Measured in-page: scrollWidth == innerWidth at 1280px and at phone width.0NULLNULLNULL02026-08-13T05:01:37Z0
sr-ht-dolt-44n.27609b97699f2d7865c8d81e2ef9d79f8a93eb2a8ca7b62075ea085ca7626e2d4Freshness line in the beads/milestones/memory headerNothing on the board says how fresh it is: with a 30s auto-push debounce and a manual pull, a store that stopped receiving pushes yesterday renders identically to a current one. Add a shared beadsHead partial rendering "<branch> · last commit <relative> · <short hash>", the hash linking to the commit page, fed by a Head *browse.CommitInfo field on the handleView envelope so every view gets it without asking.docs/DESIGN.views.md ch.3, ch.7. One Log(ctx, ref, "", 1) call. Needs the ago template func; absolute time in the title attribute.A database with no commits renders the header without one rather than failing.closed2taskNULLNULL2026-08-12T20:11:48ZEugene Blikhbigbes@gmail.com2026-08-13T05:28:23Z2026-08-13T05:28:23ZNULL0NULLNULLNULL000�{}Landed in 6459298: Head *browse.CommitInfo on the handleView envelope, the beadsHead partial, ago with a swappable clock. Empty history and a failing Log both degrade to no line and a 200 — the decoration can never be the reason a reader loses the board.0NULLNULLNULL02026-08-13T05:18:22Z0
sr-ht-dolt-44n.37ba664f882da02bf0a032cb7e237a7e829cace479e7bd3f7878c7726b24a9887browse: TableHash and the memory revision walkA memory row is (key, value) with no timestamp, so a two-month-old note is indistinguishable from one written this morning. Recover the date from history: add browse.TableHash (root.GetTable then Table.HashOf — O(1), reads no rows) to browse/ and both BrowseSession seams, then walk the log newest-first, skipping every commit whose config table hash is unchanged and reading rows only at the boundaries, to attribute each kv.memory key to the commit that last changed it.docs/DESIGN.views.md ch.2.1. Bound the walk at 500 commits (the most active tracker on this instance had 225 after three weeks, measured 2026-08-12 via dolt_log); an unresolved key renders "older than the last 500 commits" rather than a date the walk cannot support. The commit message is a claim; the table hash is the fact.Fixture history where a key is written, changed, then left alone for several commits resolves to the changing commit; the not-found-within-walk arm is covered.closed2taskNULLNULL2026-08-12T20:11:49ZEugene Blikhbigbes@gmail.com2026-08-12T20:18:34Z2026-08-12T20:18:34ZNULL0NULLNULLNULL000�{}browse.TableHash landed in 97e0f8a (root.GetTable + Table.HashOf: covers schema, rows and secondary indexes, cheaper than GetRowDataHash). The revision walk itself, and adding TableHash to the BrowseSession seams, belong to the Memory view and are specified in docs/DESIGN.views.md ch.2.1 — tracked on sr-ht-dolt-b08.0NULLNULLNULL02026-08-12T20:15:46Z0
sr-ht-dolt-44n.4740c8403d3b42789698b44c736d7b2512d0e413c0bc926b035a56afed9182726Copy-ready bd commands on the issue detail paneThe page shows work and cannot change it — that is the architecture. Hand over the command instead: a small block under the issue header with the bd commands appropriate to its status (open: --claim, close; in progress: close, --status=open; closed: reopen), each line user-select: all so one click selects a whole command.docs/DESIGN.views.md ch.6. No JavaScript and no clipboard API. The command names the issue and nothing about paths: this service does not know where the tracker is checked out, and guessing one would be inventing a fact about the reader machine.The offered commands follow the issue status; nothing is offered that bd would refuse.closed3taskNULLNULL2026-08-12T20:11:50ZEugene Blikhbigbes@gmail.com2026-08-13T05:39:13Z2026-08-13T05:39:13ZNULL0NULLNULLNULL000�{}Landed in 3b06523: status-following bd commands under the issue header, user-select: all per line, no JavaScript. Verified after the fact that 'bd reopen' exists and is the preferred spelling for a closed issue (its help says it is more explicit than 'update --status open' and emits a Reopened event), so the closed branch stands as written.0NULLNULLNULL02026-08-13T05:32:52Z0
sr-ht-dolt-44n.5b094d41863ae456425aa8ab3ff458192890cfd4439196ad6523e1ae52cebdcdd/ready: the cross-database ready pageSeventeen databases on this instance carry the beads fingerprint, and "what is ready to work" is answerable in each and nowhere across them. Add GET /ready (a page, not a View): for every database the caller may browse, check the fingerprint and collect the ready set through the shared beads/ rule, grouped by database. Filters: q, assignee, priority, db.docs/DESIGN.views.md ch.4. Bounded three ways: a per-database cache keyed by (repoID, head hash) so an unmoved head reads no rows, a 60s TTL, and a 64-database ceiling that the page reports when hit. The cache holds projections, never open browse handles. The aggregator is the one /mcp ready_work calls.A PRIVATE tracker the caller may not browse is absent rather than 403; a second request with unmoved heads performs no row reads; the ceiling reports itself.closed2taskNULLNULL2026-08-12T20:11:50ZEugene Blikhbigbes@gmail.com2026-08-13T06:44:08Z2026-08-13T06:44:08ZNULL0NULLNULLNULL000�{}Landed in 043b0fd: GET /ready over beads.ReadyAcross with the three bounds (head-hash gate, 60s TTL, 64-database ceiling), per-group freshness, filters. Mutation-checked by dropping the cache consultation and watching the read counters go 4->8.0NULLNULLNULL02026-08-13T05:55:14Z0
sr-ht-dolt-44n.6aad9c553cb6022751a8410ca2f3b0f3a9d6aa286508eb7d565cff42b301b5194Cross-database issue links in rendered textA global-tracker issue naming artifacts-nex.2 is pointing at a row in another database, and the reader has to find it by hand. Recognise <prefix>-<suffix> in descriptions, design, acceptance criteria, notes, comments and event summaries, and link those whose prefix belongs to a database the caller may browse — prefixes come from each tracker config.issue_prefix, indexed on the same head-hash/TTL basis as /ready.docs/DESIGN.views.md ch.5. Escape first, then wrap the matches, and only then mark the result template.HTML: marking stored text as HTML and running a regexp over it is how a stored payload becomes a rendered one. A prefix in a database the caller cannot see is not linked and its existence is not revealed.An id in a visible database links, one in an invisible database renders as plain text, <script> in a description stays escaped, an unknown prefix is left alone.closed3taskNULLNULL2026-08-12T20:11:51ZEugene Blikhbigbes@gmail.com2026-08-13T07:07:07Z2026-08-13T07:07:07ZNULL0NULLNULLNULL000�{}Landed in dc91829: PrefixesAcross + PrefixIndex.Scan, escape-then-wrap rendering, the index bounded by the same projectionCache the ready page uses (ReadyCache was refactored onto it, so the numbers exist once). A database the caller may not browse is never opened and its ids render as plain text indistinguishable from an unknown prefix. An ambiguous prefix links to neither — a guess rendered as a fact is worse than plain text.0NULLNULLNULL02026-08-13T06:27:39Z0
sr-ht-dolt-4y65883109e90619d5dac3d0c721a5bfdb000b1c91bef5d21879ca9d247addadcebThe milestones page presents a partial rollup as whole-tracker arithmeticweb/templates/milestones.html renders MilestoneView with no clip notice, so on a tracker past the 2000-row cap the per-milestone totals read as facts about the whole tracker. beads reports the clip (ebbd30f) and the other two surfaces render it (82d997d, a419499); this is the third and last one.closed3bugNULLNULL2026-08-13T07:57:01ZEugene Blikhbigbes@gmail.com2026-08-13T08:12:17Z2026-08-13T08:12:17ZNULL0NULLNULLNULL000�{}Landed in bbaaa1f: the detail pane's sentence and markup verbatim, placed outside the milestones/no-milestones branch — 'No milestones.' over a clipped read is the reading most easily mistaken for a fact.0NULLNULLNULL02026-08-13T07:58:03Z0
sr-ht-dolt-57a311f83a56aaf4b37747523b26df669fcd4f233808d44196e4c0c48f144b70b70Remove stale hashed stylesheet before writing a new one in make cssWhat is wrong: dolt's `Makefile` rule `static/main.min.css: static/main.css` ends with `cp $@ $(@D)/main.min.$$(sha256sum $@ | cut -c1-8).css`. Every other service's `css` recipe opens with `rm -f web/static/main.min.*.css` before writing the new hashed file; dolt's does not, so a second `make css` after a stylesheet change leaves two `main.min.<sha>.css` files on disk simultaneously. Why it matters: `assets.Resolve` takes `fs.Glob`'s first match, so with two hashed files present, which stylesheet actually gets served depends on the lexical order of two arbitrary hex digests. dolt's own `check-css` comment already states this outright ("`css` does not remove the previous build's hashed file, which is exactly why this must not assume it did"), so the packaging path is gated against it — but the ordinary dev loop and the `make css`-then-restart path are not. Sibling that already does it correctly: the other services' `css` recipes all carry an `rm -f` prologue; `assets.Resolve`'s own doc-comment assumes this invariant ("`make css` guarantees there is at most one"). Fix: add `rm -f $(CSS)` (or the equivalent glob) before the `cp` in dolt's `css` rule, or replace the `cp` with a `mv` so only one hashed file can exist at a time. Status: verified. Audit: D22 (cross-repo audit, 2026-08-17) open2bugNULLNULL2026-08-17T20:39:39ZEugene Blikhbigbes@gmail.com2026-08-17T20:39:39ZNULLNULL0NULLNULLNULL000�{}0NULLNULLNULL0NULL0
sr-ht-dolt-5boa7be3c20ae5af0a6b8e00a4765f9613215128b6354d30c123bcc9aec9cbd7562Adopt sr-ht-ecore/mcphttp instead of dolt's private mcpsrv cache/hostguard copiesWhat is wrong: `sr-ht-ecore/mcphttp` was extracted specifically because the Host guard, the commit-time cache wrapper, and the MCP transport options are byte-identical across services — and dolt still carries a private copy (`privateCache`/`cacheWriter`, `allowHosts`/`hostAllowed`, and the options literal) instead of importing it. dolt pins an ecore revision (`…20260816081411-3bd158fbb232`) that already contains the `mcphttp` package, including its Flush fix (3bd158f), so the "not resolvable" excuse other siblings' comments give does not apply here either. Why it matters: the duplication is what produced D02 (dolt's copy is missing the Flush fix) — the identical bug was independently discovered and fixed twice elsewhere in the family (ecore 3bd158f/00d7582, artifacts 6c44e03) instead of being fixed once upstream and inherited. Sibling that already does it correctly: none yet fully adopts it, but the target package is `sr-ht-ecore/mcphttp/{mcphttp,hostguard,cache}.go`. One real blocker specific to dolt: `mcphttp` currently hardcodes `vary = "Cookie, Authorization"`, while dolt deliberately uses `Authorization` alone (dolt's `/mcp` is bearer-only, an argued and correct choice for dolt) — so `vary` needs to be parameterized in `mcphttp.PrivateCache` before dolt can adopt it. Fix: once `vary` is parameterized upstream in ecore (a prerequisite, not dolt-local work), delete dolt's local `privateCache`/`cacheWriter`/`allowHosts`/`hostAllowed`, import `mcphttp`, and call `PrivateCache`/`HostGuard`/`StreamableOptions()` with `Authorization`-only vary. Keep the MCP tool-registration code itself local — that part is intentionally not shared. Status: verified — grep for `sr-ht-ecore/mcphttp` imports across all nine service trees returns zero real imports (two doc-comment mentions elsewhere, none in dolt). Audit: D10 (cross-repo audit, 2026-08-17) open2taskNULLNULL2026-08-17T20:38:58ZEugene Blikhbigbes@gmail.com2026-08-17T20:38:58ZNULLNULL0NULLNULLNULL000�{}0NULLNULLNULL0NULL0
sr-ht-dolt-5s7013f271f2040f96efdc45e4c0a9e0aa074805cd0a5e3392d06cc14a5e77c99b0Rename a database from its settings pageOwners can change a database's description and visibility but not its name; the only way to rename today is delete + recreate + re-push, which loses the ACLs and the history. Add a rename action: move the metadata row (name + path, both unique) and the on-disk NBS store together, evicting the served handle for the old path.Owner-only rename form on /~user/db/settings; name validated with core.ValidateName; a collision answers 409 and changes nothing; the row and the store dir move together (row rolled back if the move fails); the cached remotesapi handle for the old path is evicted; the browser lands on the renamed database.closed2featureEugene BlikhNULL2026-08-15T08:35:07ZEugene Blikhbigbes@gmail.com2026-08-15T08:56:09Z2026-08-15T08:56:09ZNULL0NULLNULLNULL000�{}Rename shipped: storage.MoveStore + db.RenameRepo + the settings rename action, with tests in all three layers0NULLNULLNULL02026-08-15T08:35:21Z0
sr-ht-dolt-5ywbe8e59de82460303f6bd9a46ea35a180588591706ffee95ef79617e39cfa315cA bad commit-log cursor is reported without naming the cursorIn mcpsrv get_commit_log, when from is set the ref is never resolved (it is only resolved when from is empty), so the miss message reads: ~alice/notes has no branch or commit "" — it names an empty string instead of the cursor the caller actually sent. The classification is right (an ordinary miss since cdb9195); the sentence is unhelpful. Found while pinning that classification.Name what was actually asked for: when from is set, the miss is about the cursor, not about a branch. Two different sentences, or one that carries whichever value was supplied.closed3bugNULLNULL2026-08-13T07:10:11ZEugene Blikhbigbes@gmail.com2026-08-13T07:16:04Z2026-08-13T07:16:04ZNULL0NULLNULLNULL000�{}Landed in c1d1a17: a cursor miss gets its own sentence naming the cursor and pointing at the 'next' hash a previous page returned; a ref miss keeps naming the ref and pointing at list_branches. The ref is still not resolved just to have something to print.0NULLNULLNULL02026-08-13T07:12:43Z0
sr-ht-dolt-7ta545df5bee637c3deb329ede8bcf297fe7cd1e3b36ad880466d8833fd53e0d9eeThe overview page echoes a raw browse error to the readerweb/handlers_repo.go renders the browse layer's error text into the overview page. Those strings carry dolt internals and on-disk paths, which nothing else on this surface discloses. Noticed while writing the MCP surface (dcdc979), which is careful never to let a store error reach a caller verbatim.The page shows a fixed sentence; the detail goes to the log with the repo id.closed3bugNULLNULL2026-08-13T05:24:30ZEugene Blikhbigbes@gmail.com2026-08-13T07:19:41Z2026-08-13T07:19:41ZNULL0NULLNULLNULL000�{}Landed in 11c622f: the overview carries a bool, not an error string, so no browse text can reach the dot; the page says 'Could not read history.' and the detail goes to the log with the database id. The empty-database state (No commits./No branches.) was kept — it was never the error path.0NULLNULLNULL02026-08-13T07:08:28Z0
sr-ht-dolt-8il337c621057a981c96a9ef996c9379d9a233dd548007930b646fc0efe6c092d90Move ~495 lines of inline style CSS out of beads/memory/ready/milestones templatesWhat is wrong: `sourcehut-dolt/web/templates/{beads.html (252 lines), memory.html (110), ready.html (80), milestones.html (53)}` carry roughly 495 lines of CSS total in inline `<style>` blocks, despite dolt's `scss/main.scss` being only 18 lines total and having a working `make css` pipeline. Why it matters: inline CSS is re-sent on every page load, sits outside the hashed-asset immutable cache, and cannot use the shared SCSS variables (`$font-family-monospace`, the dark palette) — so dolt's theme diverges from the rest of the instance in exactly the pages that put their rules outside the shared build. Sibling that already does it correctly: the six other services that keep everything in `scss/main.scss` below `@import "base"`. Fix: move the inline CSS rules out of `beads.html`, `memory.html`, `ready.html`, and `milestones.html` into `scss/main.scss`, and delete the `<style>` blocks from the templates. Status: verified. Audit: D37 (cross-repo audit, 2026-08-17) open3taskNULLNULL2026-08-17T20:39:53ZEugene Blikhbigbes@gmail.com2026-08-17T20:39:53ZNULLNULL0NULLNULLNULL000�{}0NULLNULLNULL0NULL0
sr-ht-dolt-9c6771e917e7a1f542d45b988a524089b9b8035acd0dbd0b2f57120017b67ed04efThe settings POST writes store paths into the responseweb/handlers_settings.go:201,206 responds with "database record removed but store deletion failed: "+err.Error() and "store deleted but cache eviction failed: "+err.Error(). That is the same leak 11c622f removed from the overview, one path along: the browse/storage error text carries on-disk paths, and the owner is shown them. Found while fixing the overview.Same shape as 11c622f: a fixed sentence to the reader, the detail to slog with the database id. The distinction that matters to the owner — the record is gone but the store may not be — should survive in the sentence without the path.closed3bugNULLNULL2026-08-13T07:19:41ZEugene Blikhbigbes@gmail.com2026-08-13T07:26:37Z2026-08-13T07:26:37ZNULL0NULLNULLNULL000�{}Landed in 35875ff: two fixed sentences that keep the distinction the owner needs (record gone but store may remain / store gone but a cached handle may survive), detail to slog with the database id. The agent also checked handlers_keys.go and left its 'Invalid public key: <err>' alone — those errors describe the caller's own submitted string, not the storage layer, which is a different class.0NULLNULLNULL02026-08-13T07:21:10Z0
sr-ht-dolt-aff6d8842504ede56b780b9fb7ac71d1dbb30600ee8ca0cfef2efcb5bbc01723207Document /mcp credential policy (bearer + meta PAT) against the family-wide MCP auth ruleWhat is wrong: `/mcp` mounting differs across services with no shared written policy. `sourcehut-dolt/cmd/doltsrht/mcp.go` uses its own bearer validator and additionally accepts a meta PAT — a fourth distinct answer alongside bench (structurally bearer-only, no cookie/resolver access), coverage (sits under the shared resolver so cookies reach it, gates reads on `p.IsInstanceToken()` only), and curator (also sits under the shared resolver via `agents.Mount(s.Resolver.Middleware())`). Why it matters: this is not by itself a hole in dolt — every tool still runs its own visibility check — but there is no single documented answer, family-wide, to "what does an MCP surface accept on this instance", and each service currently answers that question independently. dolt's own answer (bearer + meta PAT, no cookie) is plausible and is also the basis for the `Authorization`-only Vary header dolt uses (relevant to D02/D10), but it is undocumented as a deliberate policy rather than an accident of implementation. Sibling comparison: none of the four current answers is wrong per se; the defect is the absence of a written rule any of them can be checked against. coverage's split (anonymous read allowed, but only via `IsInstanceToken()` gating) is explicitly documented as intentional in its own code, which is the standard the others — including dolt — should meet. Fix: once the policy is written once, family-wide (in `sr-ht-ecore/mcphttp`'s package doc or the tokens SPEC — "MCP is bearer-only unless the tools are anonymous-readable, in which case say so at the guard"), confirm dolt's mount in `cmd/doltsrht/mcp.go` states and matches that policy explicitly (bearer + meta PAT, not cookie), including in a doc-comment on the mount itself. Status: verified by the underlying report, which explicitly retracted an earlier misreading of a sibling's (coverage's) documented behavior — treat the family-wide policy statement itself as not yet written, and dolt's own mount as needing a documented rationale once it lands. Audit: D30 (cross-repo audit, 2026-08-17) open2taskNULLNULL2026-08-17T20:39:46ZEugene Blikhbigbes@gmail.com2026-08-17T20:39:46ZNULLNULL0NULLNULLNULL000�{}0NULLNULLNULL0NULL0
sr-ht-dolt-b086314ccc71fe7047ad5a1bafc0067c236a442b3cb98c13b163cf596471ce52b2eMemory view: surface bd memories kept in the config table`bd remember` stores agent memories in the beads `config` table as ordinary key/value rows: the key is `kv.memory.<slug>`, the value is the memory text. Today they are reachable only through the generic table browser, mixed in with the tracker's tuning knobs (`compact_*`, `issue_prefix`, ...), and every memory is squeezed into a single table cell — which is unreadable for the multi-line handoff notes that make up most of them. Give them their own view, registered the same way Beads and Milestones are: a separate `View` implementation whose tab appears next to them for any beads DB. Shape it on web/milestones.go, which is the smallest existing companion view: - `web/memory.go`: `memoryView` registered from `init()` via `RegisterView`. `Name() == "memory"`, `Label() == "Memory"`, `Template() == "memory.html"`. - `Applies`: the beads fingerprint (reuse `(&beadsView{}).Applies`, as milestones does) plus a `config` table carrying `key` and `value` columns. `Applies` only sees table shapes, never rows, so a beads DB with no memories still gets the tab and renders an empty state — same contract milestones has. - `Build`: read `config` through `readRowsOptional`, keep rows whose key has the `kv.memory.` prefix, strip it for the display slug, sort by slug. Optional `?q=` substring filter over slug + text, and `?key=<slug>` for a single-memory detail, both parsed from the query values Build already receives. - Values are stored as written, so they carry both real newlines and literal `\n` two-character escapes (agents type them into shell strings). Normalise both into paragraphs before rendering rather than dumping one blob. - `web/templates/memory.html`: same flat todo.sr.ht idiom and inlined `<style>` block as milestones.html, with the `viewtabs` partial and `"Current" "memory"`. - Tests next to `web/milestones_test.go`: fingerprint accept/reject, prefix filtering (a `compact_batch_size` row must not show up), escape handling, filter and detail modes, empty state. A beads DB shows a Memory tab beside Beads and Milestones; it lists every kv.memory.* config row as a readable, multi-paragraph entry keyed by its slug; non-memory config rows never appear; a DB with no memories renders an empty state instead of a broken tab.Owns the revision walk (docs/DESIGN.views.md ch.2.1) now that browse.TableHash exists (97e0f8a): add TableHash to web/deps.go BrowseSession, walk the log newest-first skipping commits whose config table hash is unchanged, cap at 500 commits, render 'older than the last 500 commits' when a key does not resolve. The 'ago' func landed in 6459298 with chrome's unit ladder: it says '2 months ago' where ch.2.2's mockup wants 'written 71 days ago'. For judging staleness days are the informative unit, so this view needs a day-resolution spelling of its own rather than a change to 'ago' (which is shared with the freshness line).closed2featureNULLNULL2026-08-10T11:01:48ZEugene Blikhbigbes@gmail.com2026-08-13T05:53:37Z2026-08-13T05:53:37ZNULL0NULLNULLNULL000�{}Landed in 231b776: beads.BuildMemories with the bounded revision walk, the memory View after Milestones, agoDays for day-resolution staleness, stale? at 60 days. The walk was proven to skip rather than read (reads recorded at three refs out of five commits; 501 untouched commits cost one read). Real-data smoke: sourcehut-artifacts 9 memories attributed to distinct commits in 16.5 ms. Follow-up 0190aab replaced the init-order trick with an explicit registration list in views.go.0NULLNULLNULL0NULL0
sr-ht-dolt-bcpf9946f3bc8095377e0620dd72e6d2d1aa8ab9d488b7cdfe2ff1249a206fda1baThe ready, memory and prefix projections discard their row totalsbeads/ready.go, beads/memory.go and beads/prefixes.go still drop the table totals with _, so a clipped read is invisible in the cross-database ready set, in the memory list and in the prefix index. Same shape as ebbd30f fixed for the detail and milestone paths; each has its own view struct, so each needs its own field rather than a shared one (ReadyView.Capped is a different cap — it counts databases).closed4bugNULLNULL2026-08-13T07:17:47ZEugene Blikhbigbes@gmail.com2026-08-13T07:49:39Z2026-08-13T07:49:39ZNULL0NULLNULLNULL000�{}Landed in 8dfe078: per-database ReadyTruncation (recorded BEFORE the empty-group skip, so a tracker whose whole ready set sits past the cap is still named, and surviving a cache hit — both pinned), MemoryView.ConfigTruncated/ConfigShownOf named after the table so they cannot be confused with WalkTruncated (a bound in commits), PrefixIndex.Truncated. Minor known duplication: readyRead mirrors build.go's readClip because the file boundary I set forbade widening the latter's doc comment.0NULLNULLNULL02026-08-13T07:38:29Z0
sr-ht-dolt-cwlfc963bcde32da1ecdf6bc96463f664c2b27ff45930d14191a0b114968526d744Add Flush hook to mcpsrv cacheWriter so MCP answers keep Cache-Control/VaryWhat is wrong: `mcpsrv/mcpsrv.go::cacheWriter` stamps `Cache-Control: private, no-store, no-transform` and `Vary` at commit time by hooking `WriteHeader` and `Write`, but not `Flush`. A response committed through a flush never reaches `commit()` and ships with neither header; because `cacheWriter` embeds the `http.ResponseWriter` interface, a `w.(http.Flusher)` assertion in the MCP SDK also fails outright. Why it matters: this is a security defect, not a style issue. dolt's MCP surface can answer with data scoped to the caller, and without `Vary` a shared/intermediate cache keys the answer on the URL alone and can hand a flushed response to the next caller regardless of credentials. `httptest.ResponseRecorder`-based tests structurally cannot catch this — it needs a real-socket, flush-before-write test. Sibling that already does it correctly: `sr-ht-ecore/mcphttp/cache.go::cacheWriter` (commit 3bd158f) fixes this with a proper Flush hook; artifacts (6c44e03) and bench (7173a3e, test `TestHeadersLandOnAFlush`) and specs (824788a) and curator carry the fix locally. Fix: replace dolt's private `cacheWriter`/`privateCache` with `mcphttp.PrivateCache` from the ecore revision dolt already pins (see the related D10 issue on adopting mcphttp generally); failing that, add a `Flush` method that calls `commit()` then `http.NewResponseController(w.ResponseWriter).Flush()`, and add a real-socket flush test, not a recorder test. Status: verified — cacheWriter has 0 Flush methods in dolt's mcpsrv package (confirmed by counting method sets across all six mcpsrv packages). Audit: D02 (cross-repo audit, 2026-08-17) open0bugNULLNULL2026-08-17T20:38:41ZEugene Blikhbigbes@gmail.com2026-08-17T20:38:41ZNULLNULL0NULLNULLNULL000�{}0NULLNULLNULL0NULL0
sr-ht-dolt-djwf4cc5b3208357590da32454d921be996c703f6fd12be9dbc63ee0114111dbb42Render the clipped-read signal on the two surfacesbeads now reports a clipped read from the detail and milestone projections (ebbd30f) and can tell "not in what was read" from "does not exist" (Data.MissingBeyondCap). Nothing renders it yet: mcpsrv get_issue still answers a tail id with the flat noSuchIssue sentence, and the web detail pane draws no banner (the board banner is inside the board branch). Both should say the read was partial rather than claim absence.closed3taskNULLNULL2026-08-13T07:17:46ZEugene Blikhbigbes@gmail.com2026-08-13T07:56:54Z2026-08-13T07:56:54ZNULL0NULLNULLNULL000�{}Landed in a419499 (mcpsrv: get_issue answers issue:null with table_truncated/table_total for an id past the cap, and the two misses are told apart by the payload rather than by prose — the ordinary miss carries no structured content at all) and 82d997d (the web detail pane says the read was clipped, and the miss says the id may sit in the tail this page never saw). Spec amended in the follow-up commit.0NULLNULLNULL02026-08-13T07:38:21Z0
sr-ht-dolt-dwzcb4e04c6a6cc60c8bca0981fb215ea3b6f625e9090c0a7a580bdf0b5e65b8217Create databases with an empty store so the first push needs no --forceWeb create and the git-hook auto-provision (/internal/repos) both call storage.InitStore, which writes an 'Initialize data repository' commit. dolt checks fast-forward client-side (env/actions/remotes.go CanFastForward), so any client with its own history (bd's embedded Dolt) can only land its first push with --force. push-to-create already uses storage.InitEmptyStore and is proven e2e. Make both creation paths default to InitEmptyStore, add an opt-in 'initialize with an empty commit' checkbox to the web form (a store with no commits cannot be dolt-cloned: ErrNoDataAtRemote), and rewrite the empty-database overview as a push-to-get-started page.internal create always empty; web create empty by default and InitStore only with the checkbox; empty overview shows push instructions instead of the clone box; e2e proves a pre-provisioned empty store accepts a push from unrelated local history without --forceclosed2featureEugene BlikhNULL2026-08-15T08:52:41ZEugene Blikhbigbes@gmail.com2026-08-15T09:03:33Z2026-08-15T09:03:33ZNULL0NULLNULLNULL000�{}Implemented: /internal/repos and the web form default to InitEmptyStore, the form's initialize checkbox is the only path to WriteEmptyRepo, the empty overview teaches push, and remoteapi's e2e proves a pre-provisioned empty store takes an unrelated history with no --force. Follow-up for already-provisioned databases: sr-ht-dolt-24m. Not committed — awaiting review.0NULLNULLNULL02026-08-15T08:52:53Z0
sr-ht-dolt-jgv4540dae377350c5d67132704007733e4d3402f5ddc2525187240a1ebc7f98d6aSwitch /static/ mount from r.Handle to chimw.GetHeadWhat is wrong: dolt mounts `/static/` with `r.Handle(assets.DefaultPrefix+"*", s.static)`, which registers every HTTP method for that route (unlike `chimw.GetHead`, which registers only GET/HEAD). A same-origin POST to `/static/main.min.<sha>.css` is served the file with a 200 instead of getting a routed 405. Why it matters: harmless today, but it means dolt's routing tree does not describe what the service actually serves — the exact argument `chimw.GetHead`'s own doc-comment makes for registering the GET/HEAD pair on the tree instead of rewriting methods per request at handler time. Sibling that already does it correctly: `chimw.GetHead(r, assets.DefaultPrefix+"*", …)` in artifacts, bench, curator, and tokens. Fix: convert dolt's `/static/` mount from `r.Handle(...)` to `chimw.GetHead(...)`. Status: verified. Audit: D38 (cross-repo audit, 2026-08-17) open3taskNULLNULL2026-08-17T20:40:00ZEugene Blikhbigbes@gmail.com2026-08-17T20:40:00ZNULLNULL0NULLNULLNULL000�{}0NULLNULLNULL0NULL0
sr-ht-dolt-lam4f64761aa435c5d8cf89fa2abc4d7c21a0df1646ab5c3603640ea87a0a4f1f54Log a warning in web/router.go when assets.Resolve returns emptyWhat is wrong: `sourcehut-dolt/web/router.go` has an unhashed `main.css` fallback for when `assets.Resolve` returns `""` (meaning "no build artifact"), but logs nothing when that fallback is taken. `assets.Resolve`'s own doc prescribes logging in this case, and bench, compare, coverage, curator, specs, and tokens all call `slog.Warn` naming `make css` as the remedy. Why it matters: a build without `make css` starts, serves every page unstyled, and says nothing in the logs. This is precisely the failure mode D08 (hardcoded `sha256sum` producing an empty hash on non-GNU machines) and D22 (stale hashed files) can silently produce in dolt — a missing warning here is the only thing that would surface either problem operationally. Sibling that already does it correctly: bench, compare, coverage, curator, specs, and tokens all warn when `assets.Resolve` comes back empty. Fix: add an `if cssHref == "" { slog.Warn(...) }` arm to `web/router.go`, placed after dolt's existing unhashed-fallback logic (since dolt's fallback itself currently also skips logging), naming `make css` as the remedy. Status: verified. Audit: D23 (cross-repo audit, 2026-08-17) open2bugNULLNULL2026-08-17T20:39:45ZEugene Blikhbigbes@gmail.com2026-08-17T20:39:45ZNULLNULL0NULLNULLNULL000�{}0NULLNULLNULL0NULL0
sr-ht-dolt-m6301bf9d94009de75026c5dc23458e4967ef854881497fdcd59aa564926713fdb4db: enumerate the databases a caller may list across ownersThere is no instance-wide listing query today: db/repos.go has ListReposByOwner(owner, viewer) (the listing rule, anonymity included) and ListReposForDashboard(userID) (owned or ACL'd), and nothing that answers "every database this caller may list". Two consumers need exactly that: mcpsrv list_databases had to take an optional owner and refuse anonymous+no-owner rather than state something false about the instance (dcdc979), and the cross-database ready page cannot find the beads trackers without it.Add the query to db/repos.go next to its two siblings, spelled with the same listing rule ListReposByOwner documents: PUBLIC to everyone, plus anything the viewer owns or holds an ACL entry on; UNLISTED and PRIVATE never listed to a stranger. Then widen mcpsrv.Repos and web's RepoStore seam to it.A table-driven db test over PUBLIC/UNLISTED/PRIVATE x anonymous/stranger/grantee/owner; list_databases with no owner answers the caller-wide listing instead of refusing.closed2taskNULLNULL2026-08-13T05:24:22ZEugene Blikhbigbes@gmail.com2026-08-13T05:31:43Z2026-08-13T05:31:43ZNULL0NULLNULLNULL000�{}Landed in e6125fc: ListReposForViewer — ListReposByOwner's predicate minus the owner filter, anonymity spelled as viewer id 0 (unsatisfiable in both non-PUBLIC branches). Matrix asserts exact ordered listings for anonymous/stranger/grantee/both owners; mutation-checked.0NULLNULLNULL02026-08-13T05:27:41Z0
sr-ht-dolt-mcy3da1b3c737bd8688221d30015d098ac8196252e83c5b2bf43c773cff7a0f1dbeHonor SHA256SUM override in Makefile css recipe instead of hardcoded sha256sumWhat is wrong: dolt's `Makefile` `css` recipe hardcodes `sha256sum` (`mv web/static/main.min.css web/static/main.min.$$(sha256sum … | cut -c1-8).css`). On a machine without GNU coreutils, `sha256sum` does not exist, the command substitution silently yields empty, and `mv` produces `web/static/main.min..css`. Why it matters: that filename does not match the `main.min.*.css` glob `assets.Resolve` looks for, so `assets.Resolve` answers `""` and every page renders unstyled — with no error anywhere (`set -e` does not catch a failure inside `$$( )`). This is compounded in dolt specifically by D23 (dolt does not log when the resolve comes back empty). Sibling that already does it correctly: `sourcehut-bench/Makefile` and `sourcehut-curator/Makefile` define `SHA256SUM ?= sha256sum` and use `$(SHA256SUM)`, with a comment naming `make SHA256SUM="shasum -a 256" css` as the macOS invocation. Fix: add `SHA256SUM ?= sha256sum` to dolt's Makefile and substitute `$(SHA256SUM)` in the `css` recipe. Status: verified (grep confirms dolt's Makefile has 0 uses of a `SHA256SUM` variable and ≥1 raw `sha256sum` call). Audit: D08 (cross-repo audit, 2026-08-17) open1bugNULLNULL2026-08-17T20:38:57ZEugene Blikhbigbes@gmail.com2026-08-17T20:38:57ZNULLNULL0NULLNULLNULL000�{}0NULLNULLNULL0NULL0
sr-ht-dolt-o2r1eb0ca95ab0453cb70753336867a606da8716beccd28d537127bd7d2545b4871Add check-version and check-embedded-css gates to Makefile and APKBUILDWhat is wrong: dolt's `Makefile` defines no `check-version:` target, and its APKBUILD's `build()` runs only `make check-css` — `package()` re-verifies nothing. `check-version` inspects the compiled binary (`go version -m $(CHECK_BIN)`) for `vcs.revision`/`vcs.modified` and fails the build if the tree was dirty; `check-embedded-css` (also missing) greps the binary for the embedded stylesheet name and diffs it against what is on disk, catching "compiled before `make css` ran". Why it matters: dolt's own APKBUILD header comment explains that a dirty/unstamped binary "links, starts and serves" and "only ever misreports itself" — describing the exact failure mode and then never enforcing it. The family has hit this class of bug for real (a GOTMPDIR race in federation, jobs #530/#531; a `go mod download all` incident in bench, build #359), and D22 (dolt's `make css` leaving stale hashed files) and D08 (hardcoded `sha256sum`) make dolt specifically prone to shipping a binary whose embedded/served CSS silently mismatches what was built. Sibling that already does it correctly: bench, coverage, curator, and tokens all call both `check-version` and `check-embedded-css` from both `build()` and `package()`, so the gate runs against the artifact that actually ships. Fix: port `check-version`/`check-embedded-css` plus their `CHECK_BIN`/`CHECK_BINS` staged-artifact invocations from `sourcehut-tokens/Makefile` into dolt's Makefile and APKBUILD. Templating work, not new design. Status: verified (`grep -n '^check-version:'` matches nothing in dolt's Makefile). Audit: D12 (cross-repo audit, 2026-08-17) open2taskNULLNULL2026-08-17T20:39:04ZEugene Blikhbigbes@gmail.com2026-08-17T20:39:04ZNULLNULL0NULLNULLNULL000�{}0NULLNULLNULL0NULL0
sr-ht-dolt-q8cfe29b47daedc5dce09cbfa18c650a1c65b785e22d4f0d6f06f59e2d3d5b8c430loadRepoForBrowse reports a database outage as a missing databaseweb/router.go loadRepoForBrowse treats ANY error from GetRepoByOwnerAndName as not-found, so a Postgres outage renders the 404 page — a reader is told their database does not exist when the truth is that the service cannot answer right now. Found while writing mcpsrv/errors.go (dcdc979), which deliberately does not copy the conflation, so the two surfaces now differ on this one point.Distinguish db.ErrNotFound from every other error: the sentinel keeps the existing 404 (and the PRIVATE masking that depends on it), anything else is a 500 through a.fail. The masking rule is untouched — a private database must still be indistinguishable from a missing one.A fake store returning a non-sentinel error renders 500, not 404; the PRIVATE-masking tests stay green.closed2bugNULLNULL2026-08-13T05:24:29ZEugene Blikhbigbes@gmail.com2026-08-13T07:19:34Z2026-08-13T07:19:34ZNULL0NULLNULLNULL000�{}Landed in bee2050: repoLookupFailed splits db.ErrNotFound (404, masking untouched) from every other error (500, detail logged and never rendered). The same conflation in loadRepoForAdmin was fixed with it. Mutation check: routing the PRIVATE arm through the 500 path reddens three masking tests.0NULLNULLNULL02026-08-13T07:08:20Z0
sr-ht-dolt-s5e1a97ad25f8686aa211fbb6759f966e7d6545b1ee1bae2dc201f3abe9f4dc80d3Fail CI (not skip) when *_TEST_PG is emptyWhat is wrong: dolt's `.build.yml` follows the family convention where a `<SVCPREFIX>_TEST_PG` env var names a real DSN and the test suite skips cleanly when it is unset. That is correct behavior for a developer laptop but wrong for CI: dolt's CI leaves the variable unset and the DB-backed tests are silently skipped rather than run. Why it matters: bench's own README documents the measured cost of this pattern elsewhere in the family — 4.5% versus 77.2% statement coverage in `db/` depending on whether the DB tests actually ran. A sibling's green CI in this configuration says nothing about its persistence layer, and `TestSchemaAndMigrationsAgree` (the test that keeps the fresh-install and upgrade paths from diverging) is exactly the kind of test this silently skips. Sibling that already does it correctly: `sourcehut-bench/.build.yml` brings up a Postgres inside the build VM, exports the DSN, and its `test` task refuses to run with the variable empty rather than skipping quietly. Fix: bring up Postgres in dolt's `.build.yml` (mirroring bench's approach) and make dolt's `test` task fail on an empty `*_TEST_PG` rather than skip. Status: reported (measurement quoted from bench's own README, not independently re-measured against dolt's coverage numbers for this register entry). Audit: D19 (cross-repo audit, 2026-08-17) open2taskNULLNULL2026-08-17T20:39:19ZEugene Blikhbigbes@gmail.com2026-08-17T20:39:19ZNULLNULL0NULLNULLNULL000�{}0NULLNULLNULL0NULL0
sr-ht-dolt-tcia5b3bd37012658f7317a5085993430f2a209f6a80b8ae8811962d6e3cfd71ba4mcpsrv: the get_commit_log cursor comment is now stalemcpsrv/browse.go:527 says browse classifies an unparseable from-hash as a fault of its own "(it has no sentinel for it)", so a hand-written cursor takes the protocol arm. That stopped being true in 2c8903f: browse.Log now wraps it in ErrRefNotFound, and the call already routes through refMiss, so the behaviour is already right — only the comment is wrong. Fix the comment and add the test that pins the new behaviour (a garbage cursor is a tool-result miss, not an internal error).A comment next to code is a claim, and this one now reads as a limitation the code no longer has.closed3taskNULLNULL2026-08-13T06:35:06ZEugene Blikhbigbes@gmail.com2026-08-13T07:10:04Z2026-08-13T07:10:04ZNULL0NULLNULLNULL000�{}Landed in cdb9195. The agent also found the mcpsrv fake itself was wrong — its Log returned an unwrapped error for a bad cursor, i.e. it reproduced the very bug the stale comment described, so the comment fix alone would not have made the new test pass.0NULLNULLNULL02026-08-13T07:06:29Z0
sr-ht-dolt-tmzeb5116fe83783ab2ddf1e441205b41872fdf766ca7e2f9fdd514df4159a39defGraphQL read API for dolt.sr.ht at /querydolt.sr.ht has no GraphQL surface at all: /query is a 404, so nothing on the instance that speaks SourceHut GraphQL can read it, and it cannot federate into api.sr.ht. Add a read-only schema following the in-family donor spec.sr.ht (gqlgen + core-go's WithSchema): databases/database with cursor pagination, me, version, owner-visible ACL, plus branches, the commit log and the table list. Rows and diffs stay on /mcp, where the clipped-read semantics live and where the types are least settled.Donor: ~/data/home/sourcehut-specs/graph/ (gqlgen 0.17.36, schema.graphqls + resolvers + Options-struct server, mounted via core-go). Reads go through the same browse/ session the web and MCP surfaces use, so the three cannot disagree. Access is the existing bearer plane (meta PAT scoped dolt.sr.ht/repos:RO, tokens.sr.ht dolt:read) plus core.Allowed per database — NOT spec.sr.ht's single-owner gate, because dolt is multi-user with PUBLIC/UNLISTED/PRIVATE and ACLs.POST /query answers an introspection query and { databases { results { name visibility } } }; a caller sees exactly what the web UI would list for them, anonymous included; /query/api-meta.json serves a non-null scopes list; api-origin is documented for federation.closed2featureNULLNULL2026-08-15T09:42:53ZEugene Blikhbigbes@gmail.com2026-08-15T10:19:36Z2026-08-15T10:19:36ZNULL0NULLNULLNULL000�{}Closed0NULLNULLNULL0NULL0