feat: attribute the element history to the editor who made each change #296
No reviewers
Labels
No labels
bug
enhancement
needs-triage
ready-for-agent
ready-for-human
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Cloonar/deckle!296
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "afk/295"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #295
What
Each change is now committed in the name of the editor who made it, as a pseudonym the host supplies. A branch-mode publish keeps those commits in main's history. The History sheet shows, for each element, who published and who edited it, and who has unpublished edits.
internal/server/mutation.gorequestEditor, the one owner of this rule):harness serve --trust-editor-header(off by default) honoursX-Deckle-Editor: Name <email>.request.badEditor.gitrepo.DefaultIdentity: config, elsedeckle <deckle@localhost>).gitrepo):CommitDraft,SnapshotWIP,PublishAll/PublishPathsandSynctake an authorIdentity.deckle: carry over unpublished draft changes).%aNeverywhere, somailmap.fileresolves display names.HistoryVersion.Editslists a merge'sp1..p2commits that touched the needle's files.DraftEditsdoes the same formain..draft.locateElement+elemops.DiffElement, now behind oneelementChangehelper that entries, edits and the pending row all share.authors([author]for a single-parent commit), and so doespending.formatList); the pending row reads "Edited by …". Strings are in en and de.docs/architecture.md,docs/api.md(Writes convention, the historyauthorsfields, the error),docs/harness.md(the flag) anddocs/host-navigation.md("The editor's identity": the header, stripping it, the mailmap, the flag).scaffold/site/AGENTS.mdgets one line saying the flag is host-only.Decisions made during implementation, beyond the issue
TestBranchPublishOntoMovedMainKeepsDeveloperChange.UndoParentoffers no undo when the tip is a carry-over commit. Its parent is the publish commit, so an undo would drop unpublished draft work. This matches "a collapse drops the undo of the last published step".pending.authorsis resolved through the mailmap too (gitrepo.AuthorName,git check-mailmap --stdin), so a user shows the same name for their uncommitted work as for their commits.GET /api/element/historya trusted malformed header is refused for every element, not only when a pending row exists. Its uid-repairing GETs are in the mutation set and refuse it too.authorsis exactly[author], so standalone deckle looks as it does today.Verification
go vet ./...,go test ./...andgofmtare clean.cd web && npm run build && npx tsc --noEmit && npm test: 126 files, 3275 tests passing.ServeHTTP: header trusted, untrusted, malformed and absent on saves, publish and sync; per-element authors (two editors on two elements of one page, a third editor touching the file without changing either); newest-first order and de-duplication; pending authors in branch and WIP mode; mailmap.parseServeArgs.docs/harness.md, routes ⇔docs/api.md, docs links/budget, skills, AGENTS.md) are green.commit-treewith two-pand split author/committer environment variables works.%aNandcheck-mailmaphonourmailmap.file.p1..p2excludes shared commits.🤖 Generated with Claude Code
[autoland] verdict: pass
Lander verdict: CONCERNS (non-blocking), approved, not merged
Signal relied on: CI aggregate
success(ci / build-and-test:npm ci && npm run build && npm test,go vet ./...,go test ./...,go build). That covers every drift test (cmd/harnessusage,internal/serverroutes,docs/docs_test.go,scaffold/skills_test.go,internal/element/agentsdoc_test.go). I also ran two checks CI doesn't:gofmt -lon the changed Go files (clean) andnpx tsc --noEmitinweb/(clean).Conventions: the title is Conventional Commits,
Closes #295is present, and every commit is conventional. The diff matches the issue's scope (identity, merge publish, per-element authors, client, docs and ADR 0024). README and skills are untouched, as the issue decided. The branch is based on the currentmaintip (b0a7d28), so there is no conflict.Concern (non-blocking):
carryDraftOversilently falls back to the old draft treeinternal/gitrepo/publish.go:419-427(carryDraftOver): if main moved under the draft and no reconcile has run, and thengit merge-baseor the policy replay (policyRebase(pub, mb, head)) fails, the error is dropped.keepstayshead's tree, and the draft moves to a carry-over commit with that tree on top of the publish commit.content/orassets/media/that the draft never touched now differs between draft and main. The nextPublishAllwould publish it back as a revert.setUnsynced("published, but draft branch … could not be rebased onto main")and left the draft alone. This path calls nosetUnsyncedand writes no log.TestBranchPublishOntoMovedMainKeepsDeveloperChangeonly covers the replay succeeding.Suggested fix: when the replay fails, keep the old behaviour. Set unsynced with a reason and leave the draft for reconcile, or at least log and set unsynced before falling back. Add a test that forces the replay to fail.
The path is rare: the policy auto-resolves conflicts, so a failure needs a git-level error. That is why this is a concern and not a FAIL. Everything else checked out: author/committer split, merge parents, fast-forward and carry-over, lease push,
requestEditoras the single owner,elementChangeshared by entries, edits and pending, mailmap via%aN/check-mailmap, client strings in en and de, and docs in their owning documents.Addressed the lander's concern in
8839ad4.Finding confirmed, suggested fix declined. The silent fallback in
carryDraftOverwas real: a failed replay kept the old draft tree, leaving a draft that descends frommainbut lacks whatmaingained, which no reconcile replays and the next publish reverts. But "keep the old behaviour" (leave the draft, set unsynced, let reconcile catch up) is unsafe under the merge publish: the draft tip is already the merge's second parent, soReconcilefindsmain..tipempty, collapses the draft tomain's tree and drops the unpublished remainder (a first version of the test showed exactly that:reconcile status collapsed).What the fix does instead. The carry-over tree is settled before the push (
carryOverTree). A replay that cannot complete fails the publish while no ref has moved, exactly like a rejected push; the same request again retries.carryDraftOvernow only moves the draft onto a tree it was handed.TestBranchPublishOntoMovedMainFailsWhenReplayFailsforces the failure with a refusingpre-rebasehook and asserts: publish errors with "publish not pushed … could not be replayed", originmainuntouched, draft tip untouched, clean checkout, sync status unchanged; then removes the hook and the retry publishes with the developer's change carried into the draft.docs/architecture.mdstate the ordering and why the old-tree fallback is wrong. The PR body's "nothing is lost" claim about the fallback no longer applies: there is no fallback.go vet ./...,go test ./...andgofmtare clean; the API needs no change (a failed publish is the existingpublish.failed).[autoland] verdict: pass
Verdict: PASS.
Signal relied on: CI aggregate
successon head8839ad4(ci / build-and-test: npm build/test,go vet,go test ./...,go build), including every drift test (usage ⇔ docs/harness.md, routes ⇔ docs/api.md, docs links and budget, skills, AGENTS.md). Not re-run.Conventions: the title is Conventional Commits and
Closes #295is present. The head is based on the currentmaintip (b0a7d28), so there is no conflict. The diff matches the issue's scope.Earlier autoland concern, resolved in
8839ad4:carryOverTreenow settles the draft's carry-over tree before the push. A failed replay fails the publish with no ref moved, so the old-tree fallback is gone.TestBranchPublishOntoMovedMainFailsWhenReplayFailscovers the failure and the retry. ADR 0024 anddocs/architecture.mdare updated to match.Reviewed:
requestEditoris the single owner of the editor rule. It runs before the mutation lock and is cleared before the unlock. Publish captures it into the job.base, thenhead; single-parent whenheadis already inbase), followed by a fast-forward or a carry-over commit and a lease push.elementChangeis the one verdict for entries, edits and the pending row.DraftEditsandhistoryEditsshare one implementation.%aNandcheck-mailmap.Host assumption (ADR 0024): deckle-hosted's only
pre-receivehook refuses symlinks at the tip and does not inspect commit shape, so a merge onmainpasses it.CONCERNS: none.