Commit Graph

2 Commits

Author SHA1 Message Date
Kgothatso Ngako
2a02fbc14f docs: record the day's dry run, and the three things it changed in the plan
Phase 1 of docs/curated-to-mantra.md, run on 2026-09-13 on top of Phase 0, on a
scratch worktree that landed nothing. The plan asked for exactly this -- redo
the dry run the day you land, because the numbers drift -- and it was right to:
the upstream had moved, the pipeline as written could not do phased landings,
and the library pin turned out to have to move backwards. All three are now in
the plan, in the Phase 1 section, as a record beside the plan rather than a
rewrite of it.

**The replay lands by cherry-pick in topological order, not by
`rebase --rebase-merges`, and the reason is a property of git rather than a
preference.** The plan's phases are cuts through a graph with four merges in
it. Rebasing a cut whose merges have one parent in an earlier cut recreates
each such merge against the *pre*-replay parent -- the commit in the rewritten
source branch, not the one already landed -- and drags the unrebased lineage in
beside the rebased one. A single whole-range rebase, which is what the plan's
dry run measured, never meets this because every parent is inside the range.
So: the ordinary commits are picked one at a time, the merge commits land as
nothing, and their content, which is only ever conflict resolutions, is folded
in where the conflicts actually surface. The driver that does it is
docs/scripts/curated-replay.py, added here; every commit it lands carries a
`Pulled-From: curated/curated@<sha>` trailer stamped by the rewrite, and a
paragraph naming any resolution made on the way in.

**At a branch join the join files are taken exactly as upstream's own merge
left them, and a union was tried and rejected three times before that rule
was reached.** A union -- keep both sides of the conflict -- is the obvious
resolution for two branches appending strings to the same file, and it was
wrong three ways, each caught by the exactness check against the rewritten
tip and none of them visible in a passing build. It duplicates lines both
sides already hold when a conflict hunk widens (thirteen string keys, twice).
It never applies the other side's deletions, so the two copy-suggestion
strings that fcc19f95 removes survived. And, the one that took longest to see,
a join resolved in a different *order* from upstream's merge leaves every later
commit that edits the block unable to find its base, so its deletions fail
silently while its additions land -- which is why the second fix still left
the same two strings behind. The rule that survives: files whose lines are
unique by construction (string keys, imports, route registrations, table rows)
get a line-set three-way merge over diff3 hunks -- ours, minus what theirs
deleted from base, plus what theirs added that the file does not already hold
anywhere -- and never at a join, where the file upstream's merge produced is
the answer. The driver's docstring says all of this so the next reader does
not rediscover it.

**The library pin goes back to 59c11ed for Phases 3 and 4 and forward again
in Phase 5, and the compiler was asked before deciding.** The nsec line was
written against lightning-kmp-app 59c11ed and writes through its
`NostrKeyManager`; 84cc44c, Mantra's pin, renamed that class to a read-only
`LegacyNostrKeysFile`. Reasoning said the Phase 3 cut would not compile against
84cc44c; the trial worktree was put at that cut and built against it, and
produced eight `Unresolved reference 'NostrKeyManager'` errors in
IdentityWriter.kt. So 366b0177 moves the pin to 59c11ed, whose nested
lightning-kmp -> bitcoin-kmp -> secp256k1 chain is the same commit as
84cc44c's and rebuilds nothing native, and 5efeae76 brings it to 84cc44c --
not to the 01962f3 it named upstream, which is a branch commit since rebased
onto the library's master and no longer fetchable, but whose tree 84cc44c
reproduces exactly. Every commit on the branch will compile against the pin it
records, which is what upstream's history had and a fixed pin would have
thrown away. Alternatives rejected: adapting the Phase 3 commits to the
renamed class (rewriting upstream's work on the way in, and inventing an
intermediate state nobody built) and landing Phases 3 to 5 as one unit whose
inner commits do not build (bisect would hate it, and so would review).

**The upstream moved, and the new line is the next pull rather than part of
this one.** curated/curated went from 86cb876b, which the plan was written
against, to 29027f2b: ten commits for several profiles on one device, one of
which (759199f2) adds schema version 20, the fork's first migration. They are
recorded and left out on purpose; a migration landing on Mantra's database
deserves its own decision, and the plan's own principle -- pull the whole
non-brand tree so the next pull is a replay -- says how that decision goes
once it is made.

The trial, with the committed driver: 30 commits replayed (4, 8, 6, 12), 21
clean and 9 with a resolution note, 0 stuck; the driver's own rerun from the
Phase 0 tip reproduces the tree and improves on the hand-run trial by the one
README line the old union had wrongly kept; residual diff against the
rewritten 86cb876b exactly the known set -- Phase 0's prose, 39fb64b6's files,
808a3459's three, this document and its scripts, the logo.
:composeApp:compileDebugKotlinAndroid and :composeApp:compileKotlinJvm clean;
:composeApp:jvmTest 1,039 tests, 0 failures; :composeApp:testDebugUnitTest
530 tests, 0 failures; :composeApp:m3Audit all budgets met.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 11:57:48 +02:00
Kgothatso Ngako
b796a32be3 docs: plan the pull of Curated's work back into Mantra, and the tool that makes it mechanical
Curated (remote `curated`, curated/curated-kmp) forked from this repository at
ba26c0b1 on 2026-09-09 and has landed thirty-nine commits since: a group's own
nostr identity, curated lists over kinds 31888-31890, sign-in with a recovery
phrase, an nsec or an npub, a back button on every pushed screen -- and two
rebrands, 744 and 765 files each, underneath all of it. docs/curated-to-mantra.md
is which of that Mantra wants, in what order, and how it lands under Mantra's
names. docs/scripts/curated-unbrand.py is the rewrite the plan depends on, and
docs/README.md gets the row.

**The plan is measured, not argued, and that is its whole claim to be read.** The
obvious way to write it was to reason from the commit list about what would
conflict. Instead the method was run to the end on a scratch worktree before a
sentence was written: Curated's history rewritten into Mantra's names, replayed
onto origin/mantra with `rebase --rebase-merges`, built for both targets, and put
through jvmTest, testDebugUnitTest and the m3 audit. Every number in the document
is from that run -- 37 of 39 commits replay, 3 conflicts with known resolutions,
1,036 jvm tests and 530 common tests at 0 failures, all audit budgets met, Room
regenerating nothing -- and the appendix records it so the next reader can tell a
drifted number from a wrong method. A plan whose mechanics had not been tried
would have been a list of hopes about seven hundred renamed files.

**Mantra has not moved since the fork, and that decides the shape of the
problem.** The merge-base of the two branches is origin/mantra itself, so
`git merge curated/curated` is a fast-forward: it merges nothing and makes Mantra
become Curare, icon, package rename and the deletion of Mantra's own library,
dialects and projects sections included. The document says so before anything
else because it is the one thing git does by default. It also means the pull is
not a merge at all but a translation -- a Curated commit written in Mantra's
vocabulary applies to Mantra as if it had been written there -- which is why a
history rewrite followed by an ordinary rebase works, and cherry-picking the
original commits (every path and every import line in every context wrong) does
not.

**808a3459, which drops the translation sections, is dropped rather than
reverted afterwards, because that was measured too.** The first plan was to
replay everything and restore the sections with a forward commit written against
the final row layout. Trying the drop instead showed git merging 930d37c8,
1e52fc8f and 55664cc7's rewrite of ChatRoomDetailScreen around it without a
conflict: the screen comes out as signing key, the five identity rows, Propose
event, then Library, Dialects, Projects, then Subgroups, with `artifacts` and
`dialects` still on the UI state, and the same 1,036 tests pass --
GroupSignedWorkRowsJvmTest only asserts that Subgroups is below the block. The
one conflict is a modify/delete on a test 55664cc7 deletes anyway. A restore
commit would have been Mantra owning a layout upstream never had.

**Curated lists are pulled although they are not Mantra's product, and the
reasoning is written out in full because it is the only real product question.**
Excluding the five commits is not deleting five commits: the paste flow routes
kind 31889 beside 0, 1 and 10002, broadcast seeds an entry's relays from its
schema, the row restructure builds five rows of which two are these, the
back-button sweep touches the suggestion screens, and 165 of the 306 strings
added since the fork are theirs, interleaved. Carving that out means editing five
other commits by hand and then owning a group screen that conflicts with
upstream's on every future pull. Including it costs no schema, no migration and
no query on a screen nobody opens. The principle underneath is stated as such:
the method only stays cheap while Mantra's tree is a superset of Curated's
non-brand tree, so every feature left out is a permanent seam. If product wants
the two rows gone, that is two lines behind a constant, not surgery.

**The brand line lands as one re-messaged commit of comments, and Torch is
retired natively first.** Run through the rewrite, the two rebrand commits
collapse from 744 and 765 files to sixteen and twenty-one, and what survives is
the reasoning the rebrands added about the names they refused to change -- the
comments on TWEAK_TAG, HOST_KEY_DERIVATION_TAG and Relays.ephemeral and the
paragraph in shared-key-derivation.md. Mantra should have those; the next person
to grep "mantra/" here is as likely to finish the rename as anyone upstream was.
The rewritten d26cf6c7 is a different case: Mantra is still Torch in TorchTheme
(116 references), UserAgent, two error strings, and an iOS config that builds
Torch.app under a bundle id from two brands ago. That is Mantra's own debt and
Phase 0 pays it as MantraTheme and "Mantra", so that CurareTheme maps onto a name
that means something rather than onto the brand before last. Then d26cf6c7' is
dropped in the replay, since its work is done -- the plan was made to say this
explicitly after Phase 0 and Phase 2 were found to disagree about it.

**The normaliser is a script in docs/scripts rather than a description, because
three of its rules were wrong before they were right and prose would have hidden
that.** `\b` does not fire inside snake_case, so `\bcurare\b` misses the string
keys that carry the brand and they need look-around rules that treat `_` as a
boundary. File names carry identifiers, so a path rule that only moves
directories leaves MantraNavHost.kt and CurareNavHost.kt side by side with equal
content. And "Curated" is two words -- the brand in generation one, the
protocol's name at the tip (CuratedSchemaEvent, nostr/curated/, the queue's
"Curated" mark) -- so the bare word is deliberately not a rule and only the
generation-one identifiers are mapped, which is exact because the two commits
written in that generation never use the word as a brand in code. Every rule
matches a brand token and none matches a Mantra one, so the twelve Mantra*
entities, the two mantra/ prefixes and ephemeral.mantra.press cannot be touched by
construction; the check is that the entity-token count does not fall (433 before,
435 after, none lost) and that the rewritten tip compiles, which no missed
identifier would survive. It has a tree mode for `filter-branch --tree-filter` and
a patch mode for `format-patch` files; the consolidated script was re-run on a
fresh export of the tip and reproduces the tree that was built and tested, to the
byte, logo aside.

**The phases are cuts through the graph, not lines of work, so no commit is
replayed twice and the merges are recreated where they were.** The natural
grouping is by feature line, but the lines interleave -- the nsec branch and the
queue fork at the same commit and rejoin at c8de3a1f, the back-button branch
joins the accept flow at fedbe724 -- and two of those merges carry hand
resolutions that a flattened replay would silently lose. Each phase therefore
replays every commit reachable from its cut that the previous phase did not,
`--onto <mantra tip> <previous cut> <this cut>`, and the two conflicts and two
reconcile files that the dry run found sit in Phase 4 with their resolutions
written down as a runbook. The exactness check closes each phase: the diff between
the replay and the rewritten tip must be exactly what was chosen to drop, and
anything else is a rule that is wrong or a resolution that is, found there rather
than in production.

**Phase 6 says what the fork should become, because otherwise this document is
needed again next week.** Every future upstream commit is written in to.curare.*,
and each pull costs the dry run, the review and the prose -- affordable once,
corrosive weekly. The options are given in the order they should be taken:
converge the source package (a Kotlin package is not a brand, and the rebrands'
own messages say the domain is Mantra* everywhere that matters), then invert the
relationship so Curated is Mantra plus a thin brand-and-product series, and only
failing both keep the normaliser maintained beside the code it maps. Two things
already flow the other way and are named: 39fb64b6, since Curated still carries
the dead WalletManagerExtension.kt and one caller, and Phase 0's Torch retirement.

**Several counts in the draft were checked against the data and corrected before
they were committed.** The entity-token figure quoted from the rebrand's message
(1,369) was measured with a different pattern and is replaced by this run's own
(433 -> 435); "roughly 150 of 293" strings became 165 of 306; the back-button
commit touches eight of Mantra's translation screens, not nineteen; and the claim
that nothing in the fork added a DAO method was too strong -- three queries were
added, and the statement now says no table and no migration, with 19.json
byte-identical after a build as the evidence.

The dry run, for the record: normaliser at the tip 812 files rewritten and 806
paths moved; filter-branch over 39 commits in 84 s; replay with the logo and
808a3459 dropped 37 commits, 3 conflicts, 2 reconcile files, residual diff
exactly the logo plus the three files 808a3459 touched;
:composeApp:compileDebugKotlinAndroid and :composeApp:compileKotlinJvm clean;
:composeApp:jvmTest 1,036 tests, 0 failures (Mantra alone: 733);
:composeApp:testDebugUnitTest 530 tests, 0 failures; :composeApp:m3Audit all
budgets met with 12 adaptive uses and 2 navigation components. The scratch
worktree and the tmp/ branches were removed afterwards; the script recreates them
in under two minutes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 11:04:35 +02:00