Commit Graph

5 Commits

Author SHA1 Message Date
Kgothatso Ngako
e5d966bc6a docs(subgroups): what the implementation chose differently, and one floor that cannot move
The deviations table every doc in here carries, and it is the most-read part of
them. Seven entries, of which three are worth the reader's time.

`proposeBirthCertificate` was planned to take an `origin` parameter; it already
takes the whole `DkgSession`, so it reads `key.origin()` and nothing in the
repository or the ritual view model changed. A parameter would have been a
second source for one fact, and the proposer's copy of it at that.

Phase 3 put a button on four screens and Phase 7 replaced all four with one
`LazyListScope` extension, so they were written twice. The giveaway was already
in Phase 0.3, which argued the dialect's entry point had to be a screen because
a card cannot *grow* into a list -- the same argument applied to all four, and
the section should have been planned where the buttons were.

And Phase 8's own claim was wrong: the M3 adaptive floor cannot be raised by
this work. The floors count `MaterialTheme.breakpoint`, `BoxWithConstraints`,
the pane scaffolds and the navigation components; `readableContent()` and
`NavigateBackButton` are neither, so a screen that follows every rule in
CLAUDE.md adds nothing to either count. Raising a floor to the number the tree
happens to sit at would make it fail the next time something unrelated was
removed. The floors guard two-pane layout and the navigation shell, which a
detail screen is not, and they stay where phase 6 of the conformance work left
them. Recorded rather than quietly dropped, because the next plan will want to
make the same assumption.

Pointers from docs/README.md, from docs/subgroups.md beside the multiple-parents
line, and from docs/project-feature.md -- where a project's own room was the
first thing that file said it lacked, and which now says the rows stay in the
parent's tables all the same.

Phase 8 of docs/subgroups-for-entities.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-03 01:56:59 +02:00
Kgothatso Ngako
3cd9001c27 Merge branch 'mantra' into claude/project-feature-plan-b58ab2
Eighteen commits of subgroup adoption and key-ceremony work, and one collision
that was the point of writing Phase 0 down.

**Schema.** `mantra` took 21 for `GroupKeyStateParent` -- a room's parentage as a
row, so it can have two -- as a manual `MIGRATION_20_21` that copies before it
drops. Projects had taken 21 on this branch. Neither had shipped, so projects
renumbered to **22**, an `AutoMigration(from = 21, to = 22)` on top of theirs;
`schemas/21.json` is now theirs and `22.json` is the two together, 58 tables.
docs/project-feature.md records it, including that Phase 0 told us to check for
exactly this and checked the sibling fork rather than the base branch.

Three conflicts. `MantraDatabase.kt` wanted both entity sets and both
migrations, and their v21 contributes only a comment there since the entry is
manual. `ProposedEventTest.kt` and the schema file were additive either way.

Nine files auto-merged, and `ChatMessage.kt` was the one to watch: they reshaped
the `SubgroupBirthCertificateEvent` arm while this branch added three project
arms to the same `when`. Git saw no conflict and the compiler agrees.

**One test fails, and it arrives with the merge rather than from it.**
`NavigationRoutingTest` reports "uncaught exceptions before the test started",
which is leakage, not its own failure: `CeremonyQueueRoutingJvmTest` (new in
b6c3e3dd) builds a `ChatMessageListViewModel` over
`NO_OP_NOSTR_REPOSITORY`, whose `queueNegentropySynchronizeRequest` is a
`TODO()`, and it throws on a background dispatcher. Whichever `runTest` starts
next is blamed, so it is order-dependent -- the same test passes alone and in
smaller selections. This branch touches neither `scheduleSynchronization` nor
`NostrRepository`; its only edit to that view model is the `summarize` call.

1051 jvmTest with that one failure, 482 testDebugUnitTest clean, m3Audit's
budgets all met.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-02 20:48:17 +02:00
Kgothatso Ngako
2fb8d6921b fix(projects): let a membership's foreign key violation out, so the sweep retries
A project's memberships are foreign keys into both the project and the version
they name, so a chronicle page carrying a membership whose project is in a
*later* page cannot apply it yet. `ChronicleManager.applyPage` handles exactly
this: it catches the violation, counts the payload `failed`, and `sweep` re-runs
every page until failures stop falling.

Phase 3 broke that for projects by catching the violation inside the apply arm.
`applyInnerEvent` then returned normally, `applyPage` counted the payload
applied, the sweep saw nothing left to do, and the membership was dropped --
silently, permanently, on the device a chronicle exists for.

The catch was added so that `FrostSigningManager.applySignedEvent` would not
report a succeeded session as failed. That reasoning was already satisfied:
`applySignedEvent` wraps the call in its own try/catch. And no sibling arm
catches -- a translation chapter naming an absent chapter throws today -- so the
catch made projects the only kind a chronicle could not retry. It is gone.

Pages are 128 events or 64 KiB, and projects sort last in APPLY_ORDER, so a
boundary falling between a project and its memberships is the ordinary case for
any room with a chapter's worth of chunks in it.

`pages arriving out of order still converge` already modelled this -- one page
per payload, delivered backwards, comparing every row count on both devices --
and passed anyway, because `rowCounts` did not look at the project tables. It
does now, which is what makes that test the one that would have caught this.
`ProjectApplyJvmTest` asserts the throw rather than the swallow, and adds the
retry in miniature: refused while the project is missing, accepted once it is
there, same event both times.

1404 tests, no failures.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-02 20:05:28 +02:00
Kgothatso Ngako
aceef313ed docs: record what building projects changed about the plan
Five places the build chose differently, and one thing the plan did not
anticipate: a project reaches a chronicle only off the GroupSignedEvent record,
because the deprecated rebuild has no `toProjectEvent` and deliberately never
will. `recordSignedEvents` catches its own failure so a ceremony that succeeded
is not reported as failed, which leaves a narrow window where the row exists and
the signed event does not -- a project this device can show and can never hand
on. There is a test holding that case open.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-02 19:50:41 +02:00
Kgothatso Ngako
de4f15794a docs: a phased plan for projects, and the four keys it has to re-answer
The platform's server schema has had `project` and its two join models since
before this app existed. Bringing them here is not a port: the server answers
"whose project is this" out of a table of accounts, and this app is a group with
no accounts, no server and no authority but a quorum of its own members.

So every foreign key on `project` is re-answered. The creator becomes a `creator`
tag and the owners a set of `p` tags with an owner role -- written into the
template before it is proposed, so they are inside the id the quorum signs rather
than one device's note about who inserted a row. The owner of record is still
structurally the room, and `chatRoomId` carries that; the tags are what let a
project be the group's work and somebody else's property. Campaign is out of
scope entirely.

Two things the plan is mostly about, because neither is recoverable from the
code. The nip30303 kind band is not merely full -- 30310, 30311 and 30312 are
double-booked with the ceremony family, survivable only because none of the three
nip30303 kinds has a producer -- so projects take 30330-30332 and the collision
gets written down. And `ContributorTag.parse` drops a role that is not in
`KNOWN_ROLES`: the tag parses, the role comes back null, the string stays inside
the signed event id, the event still verifies, and the project lands with no
owners. Nothing is logged. `ROLE_OWNER` reaching that set is Phase 1's first
line, and three tests point at it.

Ten phases, 0-7 to build and 8-9 deferred. The one decision to make before Phase
1 is the scope fork: a project's contents are same-room, with real foreign keys,
because the FK is what makes a bad proposal fail loudly. Cross-room is Phase 9
and follows `parentChatRoomId`'s precedent -- a plain column, never a foreign
key, because a dangling value is the normal state.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-02 18:55:23 +02:00