@@ 1,7 1,8 @@
{"_type":"issue","id":"spec-zqb","title":"Phase 3: write plane — agents propose","description":"The agent half of the loop, and the service's whole premise: an agent proposes a document change, gets back a link, a human approves in a browser. Until this ships, agents can only read.","design":"Scope: proposals (branch proposals/\u003cid\u003e + Postgres row, state open-\u003emerged|rejected); the If-Match tree-splice merge with the four staleness cases and the ancestry check gitx flagged; the single agent token + mandatory provenance trailers; spec_propose / spec_comment over REST + MCP; every write response returns {proposal, url} so the agent can hand over a link. Prereqs found during Phases 1-2: (1) service.ListProposals(space, state) — graph proposal listing is stubbed and db only has ListProposalsByState; (2) IsAncestor(proposalHead, H) check before Merge so an already-merged proposal reports 'already merged' not a confusing 409; (3) auto-merge policy evaluation (.spec.yml AutoMerges) which the reconciler's 'approval=policy' inference also wants. Dispatch like Phases 1-2: proposal orchestration in service/ first and committed, then surface tools fan out. This is where the merge model and reconciler first run under real proposals rather than on paper.","status":"closed","priority":1,"issue_type":"feature","assignee":"Eugene Blikh","owner":"bigbes@gmail.com","created_at":"2026-07-23T04:08:58Z","created_by":"Eugene Blikh","updated_at":"2026-07-23T05:29:29Z","started_at":"2026-07-23T04:52:59Z","closed_at":"2026-07-23T05:29:29Z","close_reason":"Write plane shipped: service.Propose/Merge/Reject/ListProposals with provenance trailers, auto-merge policy, If-Match staleness + already-merged ancestry check; surfaces graph (Proposals port), mcpsrv spec_propose, and new api/ REST PUT — every write returns {proposal, url}. Validated end-to-end against Postgres. spec_comment deliberately deferred to Phase 5 (spec-by6), which gates the comment schema on the Phase 4 review UI; the approve/reject browser UI is Phase 4 (spec-3vz).","dependencies":[{"issue_id":"spec-zqb","depends_on_id":"spec-ejq","type":"parent-child","created_at":"2026-07-23T07:09:24Z","created_by":"Eugene Blikh","metadata":"{}"}],"dependency_count":0,"dependent_count":1,"comment_count":0}
{"_type":"issue","id":"spec-ejq","title":"spec.sr.ht — reviewable document storage for humans and agents","description":"A third custom Go service on the self-hosted SourceHut instance: agents propose documents, a human reviews and curates, agents read the approved text. One loop — bot produces, human curates, bots consume. Runs at https://spec.srht.bigb.es, deployed on phoebe as srht-spec-1. Code: git.sourcecraft.dev/bigbes/sr-ht-spec. Design: docs/DESIGN.md.","design":"DONE and deployed (Phases 0-2): core domain, gitx (bare repos + id-keyed tree-splice merge), db (Postgres schema + global ID registry), authn (unified-login cookie + agent tokens + provenance), the three receive hooks + hook RPC + daemon, service layer + reconciler, doc (warren vault/render on git objects), search (one global bleve index, per-line ru/en routing), projects (saved filter, not container), web read UI, mcpsrv read tools, graph read schema, prosediff (Phase 0 gate PASSED), specsrht space create/list, push-\u003ereindex. Live corpus: ~bigbes/rfcs holds SPEC-0001 and NOTE-0001. Verified end-to-end: validating push path, fail-closed reads, id-addressing, bilingual search, GraphQL. NOT DONE: the agent half of the loop (write plane), review UI, comments.","status":"open","priority":1,"issue_type":"epic","owner":"bigbes@gmail.com","created_at":"2026-07-23T04:08:44Z","created_by":"Eugene Blikh","updated_at":"2026-07-23T04:08:44Z","dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"spec-rsb","title":"CLI: specsrht token create|list|revoke","description":"db.Store has the whole agent-token lifecycle (CreateAgentToken, ListAgentTokens, RevokeAgentToken, GenerateToken/HashToken) but nothing calls it: no CLI subcommand and no web page. A freshly deployed instance therefore has no way to issue the credential the agent write plane (REST PUT and mcpsrv spec_propose) requires, short of hand-writing an INSERT with a sha256 hash. Add a 'token' admin subcommand next to 'space', in the same shape: load config, open the pool, run, exit.","acceptance_criteria":"specsrht token create \u003cname\u003e mints a token, stores only its hash, and prints the plaintext once; specsrht token list shows id/name/created/revoked; specsrht token revoke \u003cid\u003e stamps it revoked; usage errors are one clear line; unit tests cover argument parsing and row formatting","status":"in_progress","priority":2,"issue_type":"feature","assignee":"Eugene Blikh","owner":"bigbes@gmail.com","created_at":"2026-08-05T03:14:01Z","created_by":"Eugene Blikh","updated_at":"2026-08-05T03:14:05Z","started_at":"2026-08-05T03:14:05Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"spec-by6.3.5","title":"Port review UI to a line-numbered unified prose diff","description":"Replace the block-card review renderer with a real unified diff: line-numbered gutter, drag-select a line range, comment composer inline. Owner reviewed the card version against a live proposal and rejected it — labels ('ADDED PARAGRAPH') outweighed content on every row, every block carried identical chrome, and on a new file the whole page is green so the cards add noise and no signal. Split view was considered and dropped: prose lines are long and unified reads better for reflowed text. Approved prototype: https://claude.ai/code/artifact/a45e45cb-16b5-42ec-8108-84767ef9f465","design":"SELECTION IS BY LINE, ANCHORING IS BY BLOCK. Lines are what the cursor lands on; block hashes are what survive a reflow. The web layer maps a selected line range to its enclosing prosediff block and stores the existing core.CommentAnchor unchanged. service/, db/ and core/ do not move. The composer states what it will anchor to, so the indirection is visible rather than magic.\n\nTHE BLOCKING PROBLEM — word marks cannot be placed on a line for free.\nprosediff.Span is {Op, Text, Space} with no source offsets, and Tokenize\ndeliberately drops whitespace ('\\n' and ' ' both collapse to Space=true) —\nwhich is exactly what makes a rewrap invisible to the differ. So for a\nChangeModify block there is no stored answer to 'which line did this word\nchange on'.\n\nRecoverable, not free: the span script consumes the old token sequence in order\n(Equal+Delete) and the new one (Equal+Insert). Re-tokenize each source line of\nBlock.Lines, walk the script, and split a span where it crosses a line\nboundary. Needs a mapper of roughly 100 lines plus tests. Equal/Insert/Delete\nblocks need none of this — their lines map 1:1 and carry exact numbers.\n\nFALLBACK IF THE MAPPER IS NOT WANTED: render a modified block as a paired\nold/new region labelled by line RANGE rather than per line, keeping the word\nmarks. Honest and much smaller, but does not match the approved prototype.\n\nGUTTER DETAILS SETTLED IN REVIEW: one shared rail ground for both number\ntracks with a single hairline against the content (a border per cell drew a\ncage); one --row-lh shared by gutter and prose so numerals sit on the text\nbaseline rather than floating; 34px tracks; the add/delete tint starts at the\nsign column so the gutter never looks part of the change.\n\nHEADING PATH: no per-hunk breadcrumb. It restated headings visible a few rows\nabove, always so on a new file. Replaced by one sticky section readout that\nappears only once its heading has scrolled past. The path stays in the composer\nand in each thread's anchor note, where the heading is usually off screen.","acceptance_criteria":"A reviewer drags across line numbers, comments, and the thread stores an anchor identical to the one the block-card UI would have stored. Word-level marks still appear inside a modified paragraph. Unchanged lines render as context and collapse. An outdated comment still appears and is never attached to a neighbouring block.","status":"closed","priority":2,"issue_type":"feature","owner":"bigbes@gmail.com","created_at":"2026-07-25T11:40:01Z","created_by":"Eugene Blikh","updated_at":"2026-08-05T01:11:47Z","closed_at":"2026-08-05T01:11:47Z","close_reason":"Closed","dependencies":[{"issue_id":"spec-by6.3.5","depends_on_id":"spec-by6.3","type":"parent-child","created_at":"2026-07-25T14:40:01Z","created_by":"Eugene Blikh","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
-{"_type":"issue","id":"spec-ejq.2","title":"CI publish task fails: build secret apk-ci-s3 is missing","description":"Every recent builds.sr.ht run fails at the publish task while every other task passes. Confirmed on jobs #244 (e97532c), #245 (2dc6b71) and #246 (c2dd1ef): 'scss keygen version build' all SUCCESS, 'publish' FAILED. Pre-existing — #244 and #245 predate the Phase 5b work, so no code change caused it. Consequence: the apk never reaches the Garage repo bucket, so apk-mirror on phoebe has nothing new to re-index and the deployed service cannot be upgraded from CI output.","design":"EVIDENCE. .build.yml declares 'secrets: [apk-ci-s3]', documented as a File secret installed at ~/.apk-ci.env carrying APK_CI_S3_ACCESS_KEY / APK_CI_S3_SECRET_KEY for the Garage repo bucket. The publish task's first real line is '. ~/.apk-ci.env'. But 'hut builds secret list' returns exactly four secrets and none is apk-ci-s3: agent1-deploy (SSH key), bencher-api-key, s3-cache-key-secret, s3-cache-key-id. So the file the task sources is never installed.\n\nNOT VERIFIED: the exact failure text. The raw log endpoint (https://builds.srht.bigb.es/query/log/246/publish/log) needs a Bearer token, and reading hut's credential file was correctly refused, so the diagnosis rests on the secret list plus the manifest rather than on the log line itself. Read the log to confirm before acting.\n\nTWO POSSIBILITIES, needs the owner to distinguish:\n1. The secret was deleted or never created — fix is to create a File secret named apk-ci-s3 at ~/.apk-ci.env (mode 600) holding the two S3 keys.\n2. builds.sr.ht resolves manifest secrets by UUID, not by name — fix is to replace the name with the secret's UUID in .build.yml.\n\nSAME BUG IN THE SIBLING: ~/data/home/sourcehut-compare/.build.yml carries a byte-identical secrets block, so compare.sr.ht's publish is broken the same way and both fix together.","acceptance_criteria":"A push to master produces a build whose publish task succeeds and uploads the .apk to the Garage repo bucket; apk-mirror on phoebe re-indexes it within 15 minutes.","notes":"DIAGNOSIS RESOLVED 2026-08-05, still blocked on the owner.\n\nPossibility 2 (builds.sr.ht resolves manifest secrets by UUID only) is RULED OUT. buildsrht/manifest.py parses each secrets: entry as a UUID first and, on failure, treats it as a 3-512 character name and looks it up by name. So '- apk-ci-s3' in the manifest is valid as written; nothing in .build.yml needs changing.\n\nPossibility 1 confirmed: 'hut builds secret list' still returns exactly four secrets (agent1-deploy, bencher-api-key, s3-cache-key-secret, s3-cache-key-id) and none is apk-ci-s3. The secret was never created or was deleted.\n\nFailure mechanism, derived rather than read from the log: the task preamble is 'set -xe' and publish's first real line is '. ~/.apk-ci.env'. A missing file makes '.' exit non-zero and set -e kills the task. That reproduces the observed signature exactly - scss/keygen/version/build green, publish red - and matches build #250 (cc90b4a) as well as #247 (61515a5), so the port to the line-numbered diff did not change it.\n\nFIX, both steps need the owner:\n1. Garage, on the host that has the CLI: 'garage key create apk-ci' then 'garage bucket allow --read --write repo --key apk-ci'. No --owner: publish only uploads and never deletes. Check first whether the key behind the existing s3-cache-key-* secrets already has write on the repo bucket.\n2. Register a File secret on builds.srht.bigb.es (web form or GraphQL - 'hut builds secret' is list/share only, it cannot create): name apk-ci-s3, path /home/build/.apk-ci.env, mode 600, contents two lines APK_CI_S3_ACCESS_KEY=... and APK_CI_S3_SECRET_KEY=... . Absolute path rather than ~/.apk-ci.env because the worker writes the literal string it was given; /home/build is the build user's home, so the manifest's '. ~/.apk-ci.env' still resolves. Mode 600 rather than the 644 the existing s3-cache-key-* secrets use: this is a key, not an identifier.\n\nSecrets are account-level, so the same one also fixes sourcehut-compare, whose manifest carries the identical block.","status":"closed","priority":2,"issue_type":"bug","owner":"bigbes@gmail.com","created_at":"2026-07-24T21:16:35Z","created_by":"Eugene Blikh","updated_at":"2026-08-05T03:12:30Z","closed_at":"2026-08-05T03:12:30Z","close_reason":"Owner created the File secret apk-ci-s3 (/home/build/.apk-ci.env, mode 600) on builds.srht.bigb.es; 'hut builds secret list' now shows it. Resubmit of #250 as build #251 on the same commit cc90b4a: SUCCESS, all five tasks green including publish.\n\nVERIFIED: publish runs under 'set -e' and its body is a loop of 'rclone copyto' with an echo per file, so any failed upload would have exited non-zero and failed the task. A green publish is therefore evidence the .apk reached repo/alpine/v3.22/bigbes/x86_64/ — specsrht-0.0.78-r0.apk for this commit.\n\nNOT VERIFIED from here: the apk-mirror re-index on phoebe, the second half of the acceptance criteria. s3.bigb.es is not reachable anonymously from this workstation and phoebe host access is blocked (spec-ar4). If the package turns out not to be installable, that is apk-mirror's problem rather than this repo's and wants its own bead.\n\nThe manifest needed no change: '- apk-ci-s3' resolves by name, as the diagnosis note records. The same account-level secret should also unblock sourcehut-compare, whose .build.yml carries the identical block — its next build will show it.","dependencies":[{"issue_id":"spec-ejq.2","depends_on_id":"spec-ejq","type":"parent-child","created_at":"2026-07-25T00:16:35Z","created_by":"Eugene Blikh","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"spec-ejq.2","title":"CI publish task fails: build secret apk-ci-s3 is missing","description":"Every recent builds.sr.ht run fails at the publish task while every other task passes. Confirmed on jobs #244 (e97532c), #245 (2dc6b71) and #246 (c2dd1ef): 'scss keygen version build' all SUCCESS, 'publish' FAILED. Pre-existing — #244 and #245 predate the Phase 5b work, so no code change caused it. Consequence: the apk never reaches the Garage repo bucket, so apk-mirror on phoebe has nothing new to re-index and the deployed service cannot be upgraded from CI output.","design":"EVIDENCE. .build.yml declares 'secrets: [apk-ci-s3]', documented as a File secret installed at ~/.apk-ci.env carrying APK_CI_S3_ACCESS_KEY / APK_CI_S3_SECRET_KEY for the Garage repo bucket. The publish task's first real line is '. ~/.apk-ci.env'. But 'hut builds secret list' returns exactly four secrets and none is apk-ci-s3: agent1-deploy (SSH key), bencher-api-key, s3-cache-key-secret, s3-cache-key-id. So the file the task sources is never installed.\n\nNOT VERIFIED: the exact failure text. The raw log endpoint (https://builds.srht.bigb.es/query/log/246/publish/log) needs a Bearer token, and reading hut's credential file was correctly refused, so the diagnosis rests on the secret list plus the manifest rather than on the log line itself. Read the log to confirm before acting.\n\nTWO POSSIBILITIES, needs the owner to distinguish:\n1. The secret was deleted or never created — fix is to create a File secret named apk-ci-s3 at ~/.apk-ci.env (mode 600) holding the two S3 keys.\n2. builds.sr.ht resolves manifest secrets by UUID, not by name — fix is to replace the name with the secret's UUID in .build.yml.\n\nSAME BUG IN THE SIBLING: ~/data/home/sourcehut-compare/.build.yml carries a byte-identical secrets block, so compare.sr.ht's publish is broken the same way and both fix together.","acceptance_criteria":"A push to master produces a build whose publish task succeeds and uploads the .apk to the Garage repo bucket; apk-mirror on phoebe re-indexes it within 15 minutes.","notes":"RE-INDEX VERIFIED 2026-08-05, the half the close reason left open.\n\nChecked through repo.bigb.es, the public mirror apk-mirror builds from the Garage repo bucket (URL documented in the APKBUILD header; s3.bigb.es itself resolves to 192.168.88.18 and is only reachable from the LAN, which is why the first attempt to verify failed).\n\n spec.sr.ht-0.0.78-r0.apk 200 content-length 16059260 last-modified 03:09:39 GMT\n APKINDEX.tar.gz 200 last-modified 03:15:00 GMT\n index entry: P:spec.sr.ht V:0.0.78-r0 A:x86_64 S:16059260\n\nThe size in the index matches the object byte for byte, and 0.0.78 is 'git rev-list --count cc90b4a' — the commit build #251 ran on. The index was rebuilt 5.5 minutes after the upload, inside the 15-minute window the acceptance criteria name. Both halves of the criteria are therefore met.\n\nCorrection to the close reason: the package is named spec.sr.ht-0.0.78-r0.apk, not specsrht-0.0.78-r0.apk — pkgname in APKBUILD is spec.sr.ht. The specsrht spelling 404s.","status":"closed","priority":2,"issue_type":"bug","owner":"bigbes@gmail.com","created_at":"2026-07-24T21:16:35Z","created_by":"Eugene Blikh","updated_at":"2026-08-05T03:20:11Z","closed_at":"2026-08-05T03:12:30Z","close_reason":"Owner created the File secret apk-ci-s3 (/home/build/.apk-ci.env, mode 600) on builds.srht.bigb.es; 'hut builds secret list' now shows it. Resubmit of #250 as build #251 on the same commit cc90b4a: SUCCESS, all five tasks green including publish.\n\nVERIFIED: publish runs under 'set -e' and its body is a loop of 'rclone copyto' with an echo per file, so any failed upload would have exited non-zero and failed the task. A green publish is therefore evidence the .apk reached repo/alpine/v3.22/bigbes/x86_64/ — specsrht-0.0.78-r0.apk for this commit.\n\nNOT VERIFIED from here: the apk-mirror re-index on phoebe, the second half of the acceptance criteria. s3.bigb.es is not reachable anonymously from this workstation and phoebe host access is blocked (spec-ar4). If the package turns out not to be installable, that is apk-mirror's problem rather than this repo's and wants its own bead.\n\nThe manifest needed no change: '- apk-ci-s3' resolves by name, as the diagnosis note records. The same account-level secret should also unblock sourcehut-compare, whose .build.yml carries the identical block — its next build will show it.","dependencies":[{"issue_id":"spec-ejq.2","depends_on_id":"spec-ejq","type":"parent-child","created_at":"2026-07-25T00:16:35Z","created_by":"Eugene Blikh","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"spec-by6.3.3","title":"web: commentable diff — context blocks, anchor ids, comment UI","description":"renderDocDiff currently skips ChangeEqual ('the review shows only what changed', web/diff.go:61). It must now render unchanged blocks as commentable context — collapsed or dimmed so the diff still reads as a diff — because the settled scope is that any block of a proposed document can be commented on. Every rendered block gains a stable DOM id derived from the anchor tuple. Then the comment UI itself: existing comments rendered beside their block with their anchor state (moved / outdated shown honestly), a compose form, reply and resolve. Threading is flat per anchor.","status":"closed","priority":2,"issue_type":"task","owner":"bigbes@gmail.com","created_at":"2026-07-24T18:57:14Z","created_by":"Eugene Blikh","updated_at":"2026-07-24T19:59:27Z","closed_at":"2026-07-24T19:59:27Z","close_reason":"Context blocks + anchor ids + thread UI; lost-anchor area; stale-block 409. Committed 472bcb1.","dependencies":[{"issue_id":"spec-by6.3.3","depends_on_id":"spec-by6.3.2","type":"blocks","created_at":"2026-07-24T21:57:29Z","created_by":"Eugene Blikh","metadata":"{}"},{"issue_id":"spec-by6.3.3","depends_on_id":"spec-by6.3","type":"parent-child","created_at":"2026-07-24T21:57:14Z","created_by":"Eugene Blikh","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"spec-by6.3.2","title":"service: comment API + policy auto-merge gate","description":"service.Comment type, Comment/ListComments/ReplyComment/ResolveComment, and the anchor resolution pass that takes a revision's prosediff blocks and reports each comment as anchored, moved or outdated. Wire the merge gate: an unresolved comment suppresses policy auto-merge (not manual approve) — the check belongs beside the existing auto-merge policy evaluation, and needs a test that a commented proposal stops auto-merging while Merge with human approval still succeeds.","status":"closed","priority":2,"issue_type":"task","assignee":"Eugene Blikh","owner":"bigbes@gmail.com","created_at":"2026-07-24T18:57:05Z","created_by":"Eugene Blikh","updated_at":"2026-07-24T19:15:24Z","started_at":"2026-07-24T19:09:52Z","closed_at":"2026-07-24T19:15:24Z","close_reason":"service.CommentOn/ReplyTo/ResolveThread/Threads + AnchorThreads/AnchorOf; auto-merge gate in autoMerges. 10 tests, PG-backed ones green against real PG 16.","dependencies":[{"issue_id":"spec-by6.3.2","depends_on_id":"spec-by6.3","type":"parent-child","created_at":"2026-07-24T21:57:05Z","created_by":"Eugene Blikh","metadata":"{}"},{"issue_id":"spec-by6.3.2","depends_on_id":"spec-by6.3.1","type":"blocks","created_at":"2026-07-24T21:57:28Z","created_by":"Eugene Blikh","metadata":"{}"}],"dependency_count":1,"dependent_count":2,"comment_count":0}
{"_type":"issue","id":"spec-by6.3.1","title":"Comment anchors: core type + db schema","description":"Foundation wave. core.CommentAnchor (doc_id, heading_path, ordinal, hash) plus the anchor-state vocabulary (anchored | moved | outdated) and the resolution function, kept free of a prosediff import so core stays dependency-free — resolution takes the block fields it needs, not the Block type. migrations/0004_comment.sql + schema.sql: the comment table storing the tuple, threading via parent_id, author identity + agent provenance (mirroring proposal's ck_proposal_provenance rule), and resolved timestamp. Anchor state is NOT a column — it is derived per revision at read time. db/comment.go with the insert/list/resolve queries and a test that a reflow keeps the comment and a deletion outdates it.","status":"closed","priority":2,"issue_type":"task","assignee":"Eugene Blikh","owner":"bigbes@gmail.com","created_at":"2026-07-24T18:57:02Z","created_by":"Eugene Blikh","updated_at":"2026-07-24T19:08:45Z","started_at":"2026-07-24T18:57:37Z","closed_at":"2026-07-24T19:08:45Z","close_reason":"core.CommentAnchor + ResolveAnchor with 9 tests; migrations/0004_comment.sql + schema.sql; db/comment.go with 8 Postgres-backed tests, all green against real PG 16.","dependencies":[{"issue_id":"spec-by6.3.1","depends_on_id":"spec-by6.3","type":"parent-child","created_at":"2026-07-24T21:57:01Z","created_by":"Eugene Blikh","metadata":"{}"}],"dependency_count":0,"dependent_count":1,"comment_count":0}