Repository navigation
01a10b01 - Add member habits with comments and Bitcoin invoices - #399
Conversation
|
dfx pr guard EN: DE: Details
|
997dc28 to
90e0704
Compare
|
EN: DE: DetailsHead: Lane Final result: {
"schema": "a38-review/v2",
"head": "a40fd6ddd67835ce99218795110d4a1e6c94e67d",
"passes": 1,
"defects": 0,
"set_aside": "none",
"lanes": [
{"id": "conformity-a", "result": "pass", "status": "complete", "provider": "xAI", "model": "grok-4.6", "model_number": "grok-4.6", "prompt": "Review the complete PR diff for conformity with CONTRIBUTING.md."},
{"id": "logic-a", "result": "pass", "status": "complete", "provider": "xAI", "model": "grok-4.6", "model_number": "grok-4.6", "prompt": "Review the complete PR diff for logical correctness."}
]
}The pair read the diff against the base rules and the acceptance criteria. No defect remained. Coverage was not executed in the review lane. The open pull request checks on this head were already green. |
|
EN: DE: Details{
"base": "316d4fb2b04008628458c62c8aacc91ed4fda47c",
"head": "a40fd6ddd67835ce99218795110d4a1e6c94e67d",
"phase": "applied",
"pr": 399,
"reasons": [],
"repo": "21gifts/api",
"state": "ready"
} |
|
EN: DE: DetailsHead: Lane Final result: {
"schema": "a38-review/v2",
"head": "a8441b3dbeba99f24004ff1b9a3f72503a62c467",
"passes": 1,
"defects": 0,
"set_aside": "none",
"lanes": [
{"id": "conformity-a", "result": "pass", "status": "complete", "provider": "xAI", "model": "grok-4.6", "model_number": "grok-4.6", "prompt": "Review the complete PR diff for conformity with CONTRIBUTING.md."},
{"id": "logic-a", "result": "pass", "status": "complete", "provider": "xAI", "model": "grok-4.6", "model_number": "grok-4.6", "prompt": "Review the complete PR diff for logical correctness."}
]
}The pair read the diff against the base rules and the acceptance criteria, including the commit that writes each habit wording change as one statement. No defect remained. Coverage was not executed in the review lane. The open pull request checks on this head were already green. |
|
EN: DE: Details{
"base": "316d4fb2b04008628458c62c8aacc91ed4fda47c",
"head": "a8441b3dbeba99f24004ff1b9a3f72503a62c467",
"phase": "applied",
"pr": 399,
"reasons": [],
"repo": "21gifts/api",
"state": "ready"
} |
|
EN: DE: DetailsHead: Lane Final result: {
"schema": "a38-review/v2",
"head": "629229053bdd7cb3676dfaba4629187c85d4f3d5",
"passes": 1,
"defects": 0,
"set_aside": "none",
"lanes": [
{"id": "conformity-a", "result": "pass", "status": "complete", "provider": "xAI", "model": "grok-4.6", "model_number": "grok-4.6", "prompt": "Review the complete PR diff for conformity with CONTRIBUTING.md."},
{"id": "logic-a", "result": "pass", "status": "complete", "provider": "xAI", "model": "grok-4.6", "model_number": "grok-4.6", "prompt": "Review the complete PR diff for logical correctness."}
]
}The pair read the full diff against the base rules and the acceptance criteria. Editing a habit's wording now checks the owner in the same statement as the revision, and a missing or unowned habit writes nothing. Archive and log still read the row first, because that read decides missing or closed before a write that does not insert a revision. No defect remained. |
|
EN: DE: Details{
"base": "316d4fb2b04008628458c62c8aacc91ed4fda47c",
"head": "629229053bdd7cb3676dfaba4629187c85d4f3d5",
"phase": "applied",
"pr": 399,
"reasons": [],
"repo": "21gifts/api",
"state": "ready"
} |
|
EN: DE: DetailsHead: Lane Final result: Comment deletion uses the existing rank check only. Both lanes reported no defects on this head. {
"schema": "a38-review/v2",
"head": "cf05ea3e7813f16cc88ae0b735a2e407305fc975",
"passes": 1,
"defects": 0,
"set_aside": "none",
"lanes": [
{"id": "conformity-a", "result": "pass", "status": "complete", "provider": "xAI", "model": "Grok", "model_number": "grok-4.6", "prompt": "Review the complete PR diff for conformity with CONTRIBUTING.md."},
{"id": "logic-a", "result": "pass", "status": "complete", "provider": "xAI", "model": "Grok", "model_number": "grok-4.6", "prompt": "Review the complete PR diff for logical correctness."}
]
} |
|
EN: DE: Details{
"base": "316d4fb2b04008628458c62c8aacc91ed4fda47c",
"head": "cf05ea3e7813f16cc88ae0b735a2e407305fc975",
"phase": "applied",
"pr": 399,
"reasons": [],
"repo": "21gifts/api",
"state": "ready"
} |
|
EN: DE: DetailsHead: Lane Final result: The first pass on this head found one wrong citation: the wallet sentence was attributed to a forum note with no author. The pull request text now cites the wallet sentence, the zap refusal, and the habit route's own 409. Name, description, notes, and comments are limited by Unicode code points, the same count as PostgreSQL char_length. Both lanes then reported no defects. {
"schema": "a38-review/v2",
"head": "5969f74ea1f5027face0b8d9f16c184566f8cb7f",
"passes": 2,
"defects": 0,
"set_aside": "none",
"lanes": [
{"id": "conformity-a", "result": "pass", "status": "complete", "provider": "xAI", "model": "Grok", "model_number": "grok-4.6", "prompt": "Review the complete PR diff for conformity with CONTRIBUTING.md."},
{"id": "logic-a", "result": "pass", "status": "complete", "provider": "xAI", "model": "Grok", "model_number": "grok-4.6", "prompt": "Review the complete PR diff for logical correctness."}
]
} |
|
EN: DE: Details{
"base": "316d4fb2b04008628458c62c8aacc91ed4fda47c",
"head": "5969f74ea1f5027face0b8d9f16c184566f8cb7f",
"phase": "applied",
"pr": 399,
"reasons": [],
"repo": "21gifts/api",
"state": "ready"
} |
|
EN: DE: DetailsHead: Lane Lane Final result: {
"schema": "a38-review/v2",
"head": "f18ae18db28bc1222536b1e1431d1fd4c998260b",
"passes": 1,
"defects": 0,
"set_aside": "none",
"lanes": [
{
"id": "conformity-a",
"result": "pass",
"status": "complete",
"provider": "xAI",
"model": "Grok",
"model_number": "grok-4.6",
"prompt": "Read CONTRIBUTING.md and REVIEW.md at the base revision. Review this pull request against those files and against the linked issue. Do not change any files."
},
{
"id": "logic-a",
"result": "pass",
"status": "complete",
"provider": "xAI",
"model": "Grok",
"model_number": "grok-4.6",
"prompt": "Read CONTRIBUTING.md and REVIEW.md at the base revision. Review this pull request against those files and against the linked issue. Do not change any files."
}
]
} |
|
EN: DE: Details{
"base": "316d4fb2b04008628458c62c8aacc91ed4fda47c",
"head": "f18ae18db28bc1222536b1e1431d1fd4c998260b",
"phase": "applied",
"pr": 399,
"reasons": [],
"repo": "21gifts/api",
"state": "ready"
} |
|
EN: DE: DetailsHead: Lane Lane Final result: {
"schema": "a38-review/v2",
"head": "f9878c47ddedc0537aac0b7dd6db7ee50af033a5",
"passes": 1,
"defects": 0,
"set_aside": "none",
"lanes": [
{
"id": "conformity-a",
"result": "pass",
"status": "complete",
"provider": "xAI",
"model": "Grok",
"model_number": "grok-4.6",
"prompt": "Read CONTRIBUTING.md and REVIEW.md at the base revision. Review this pull request against those files and against the linked issue. Do not change any files."
},
{
"id": "logic-a",
"result": "pass",
"status": "complete",
"provider": "xAI",
"model": "Grok",
"model_number": "grok-4.6",
"prompt": "Read CONTRIBUTING.md and REVIEW.md at the base revision. Review this pull request against those files and against the linked issue. Do not change any files."
}
]
} |
|
EN: DE: Details{
"base": "316d4fb2b04008628458c62c8aacc91ed4fda47c",
"head": "f9878c47ddedc0537aac0b7dd6db7ee50af033a5",
"phase": "applied",
"pr": 399,
"reasons": [],
"repo": "21gifts/api",
"state": "ready"
} |
…error. A habit invoice that omits the amount, or sends one that is not a number, returns the same visible error as a gift. A failed mint names the Lightning Address, and the new tables reference the account and the habit.
The owner predicate sits in the same statement as the wording update and the period revision. A missing or unowned habit writes nothing.
The wording update no longer reads the row first, so the owner mismatch on the remaining read has to be reached from archive and log.
A comment id that is not a UUID is Not found before the store, and a well-formed id that is missing is the same response. Revision name and description get the same length checks as the habit row, including a table created before those checks.
Creating the table twice never reached the follow-up constraint, so a revision table from before those checks could ship without them.
A habit gift returns the payment request only when it decodes to exactly the requested millisatoshis, the same check the existing pay invoice already uses.
f9878c4 to
2b783d3
Compare
|
EN: DE: DetailsHead: Lane Final result: {
"schema": "a38-review/v2",
"head": "b1d68535777393483351c11d66dd4e9b085a7f3a",
"passes": 1,
"defects": 0,
"set_aside": "none",
"lanes": [
{
"id": "conformity-a",
"result": "pass",
"status": "complete",
"provider": "xAI",
"model": "Grok",
"model_number": "grok-4.6",
"prompt": "Review the complete PR diff for conformity with CONTRIBUTING.md."
},
{
"id": "logic-a",
"result": "pass",
"status": "complete",
"provider": "xAI",
"model": "Grok",
"model_number": "grok-4.6",
"prompt": "Review the complete PR diff for logical correctness."
}
]
}This pass reports no defects. |
|
EN: DE: DetailsHead: Lane Final result: {
"schema": "a38-review/v2",
"head": "b1d68535777393483351c11d66dd4e9b085a7f3a",
"passes": 1,
"defects": 0,
"set_aside": "none",
"lanes": [
{
"id": "conformity-b",
"result": "pass",
"status": "complete",
"provider": "OpenAI",
"model": "GPT-5.6 Sol",
"model_number": "gpt-5.6-sol",
"prompt": "Review the complete PR diff for conformity with CONTRIBUTING.md."
},
{
"id": "logic-b",
"result": "pass",
"status": "complete",
"provider": "OpenAI",
"model": "GPT-5.6 Sol",
"model_number": "gpt-5.6-sol",
"prompt": "Review the complete PR diff for logical correctness."
}
]
}This pass reports no defects. |
|
A38 recheck |
|
EN: DE: DetailsHead: Lane Final result: {
"schema": "a38-review/v2",
"head": "b1d68535777393483351c11d66dd4e9b085a7f3a",
"passes": 1,
"defects": 0,
"set_aside": "none",
"lanes": [
{
"id": "conformity-a",
"result": "pass",
"status": "complete",
"provider": "xAI",
"model": "Grok",
"model_number": "grok-4.6",
"prompt": "Review the complete PR diff for conformity with CONTRIBUTING.md."
},
{
"id": "logic-a",
"result": "pass",
"status": "complete",
"provider": "xAI",
"model": "Grok",
"model_number": "grok-4.6",
"prompt": "Review the complete PR diff for logical correctness."
}
]
}This pass reports no defects. |
|
EN: DE: DetailsHead: Lane Final result: {
"schema": "a38-review/v2",
"head": "b1d68535777393483351c11d66dd4e9b085a7f3a",
"passes": 1,
"defects": 0,
"set_aside": "none",
"lanes": [
{
"id": "conformity-b",
"result": "pass",
"status": "complete",
"provider": "OpenAI",
"model": "GPT-5.6 Sol",
"model_number": "gpt-5.6-sol",
"prompt": "Review the complete PR diff for conformity with CONTRIBUTING.md."
},
{
"id": "logic-b",
"result": "pass",
"status": "complete",
"provider": "OpenAI",
"model": "GPT-5.6 Sol",
"model_number": "gpt-5.6-sol",
"prompt": "Review the complete PR diff for logical correctness."
}
]
}This pass reports no defects. |
|
A38 recheck |
|
EN: DE: Details{
"base": "596fc9bb4170b14b3d6e1244c2e9e3d6ddcc74cc",
"head": "b1d68535777393483351c11d66dd4e9b085a7f3a",
"phase": "applied",
"pr": 399,
"reasons": [],
"repo": "21gifts/api",
"state": "ready"
} |
EN:
Any signed-in member can add, edit, archive, and rate their own daily or weekly habit, with the public wording kept on the period it belonged to and private notes returned only to the owner.
A week becomes ratable at 08:00 on the following Monday in the habit's time zone, and daily habits can be rated through today.
Comments, comment deletion, and a Bitcoin invoice for someone else's comment use the existing Sunday pause, and the API does not pay the invoice.
Deploy this API before the app, and pass the SQL habit store from boot into the server so a restart does not drop habits.
DE:
Jedes angemeldete Mitglied kann eine eigene tägliche oder wöchentliche Gewohnheit anlegen, ändern, archivieren und bewerten, wobei der öffentliche Wortlaut am Zeitraum bleibt und private Notizen nur die Person sieht, der die Gewohnheit gehört.
Eine Woche wird am folgenden Montag um 08:00 in der Zeitzone der Gewohnheit bewertbar, und tägliche Gewohnheiten können bis heute bewertet werden.
Kommentare, das Löschen von Kommentaren und eine Bitcoin-Rechnung für den Kommentar einer anderen Person nutzen die bestehende Sonntagspause, und die API bezahlt die Rechnung nicht.
Diese API vor der App ausrollen und den SQL-Speicher aus dem Boot an den Server geben, damit ein Neustart die Gewohnheiten nicht verliert.
Details
Habits are stored in
member_habit,member_habit_revision,member_habit_log, andmember_habit_comment. Adding a habit and editing its wording each use one SQL statement, so a failure cannot leave the habit row without the revision that belongs to that write. Cadence is chosen at creation and does not change. The habit stores the IANA time zone from theTime-Zoneheader on add.Public list is
GET /habits. Writes arePOST /habits: add, edit, archive, log (achieved,partial,missed), comment, delete comment, and invoice. Add returns the new id. A comment post does not return an id. The invoice response is{ pr, amountSats }, with the same 10_000_000 sat ceiling and the sameToo many paymentslimit as a forum gift. Acceptance criteria: https://github.com/21gifts/intern/issues/43. A missing Lightning address uses the existing wallet sentence, not the forum note's "This message cannot be paid yet", because a habit comment has no Nostr event. The status is 409 because 400 on this route is the amount error. Donating to one's own comment is a different job from refusing to message oneself, so that sentence stays. A closed period is rejected, including an edit afterlastPeriod. An edit or a log for a habit the caller does not own is not found, before that period is checked. Name, description, notes, and a comment are limited by Unicode code points, the same count PostgreSQLchar_lengthuses, so one emoji counts as one.Comment deletion requires initiator rank. A moderator has that same rank. Sunday rest is the existing
Time-Zonecheck: Sunday in that zone refusescomment,deleteComment, andinvoicewithSUNDAY_RESTbefore the amount check. A missing or invalid zone does not refuse. A comment id that is not a UUID is{ error: 'Not found' }with status 404 ondeleteCommentandinvoice, before the store is asked, and it is not an outage. Revisionnameis 1–80 code points anddescriptionis at most 2000, the same checks as the habit row. A table created before those checks receives them the next time the schema runs. The invoice uses the comment author's current Lightning address. Visitor-facing app copy does not say Lightning or sats.Reused:
src/lib/sunday-rest.ts:10isSundayRestHeader— Sunday in theTime-Zoneheader. Missing, blank, or invalid does not count as Sunday. Used for comment, deleteComment, and invoice only.src/lib/config.ts:38GIFT_INVOICE_MAX_MSAT— 10_000_000 sat ceiling, same constant as a forum gift.src/lib/gift-invoice.ts:40requestGiftInvoice— creates the BOLT11 invoice. This route does not pay it.src/lib/nostr/rate-limit.ts:124InvoiceRateLimiter— same 1/10s and 20/h policy. The habit route keeps its own instance, so a habit invoice does not spend the forum counter.src/routes/messages.ts:3066— forum gift response{ pr, amountSats }.src/routes/messages.ts:2944— forumToo many payments.src/routes/pos.ts:74—Expected a JSON body with an integer "amountSats".src/routes/messages.ts:142AUTHOR_WALLET_CANNOT_RECEIVE— the sentence "The author's wallet cannot receive this Bitcoin payment". A forum gift uses it when the wallet cannot mint a zap (src/routes/messages.ts:3015andsrc/routes/messages.ts:3044), both status 400.src/routes/messages.ts:2804is a note with no author, not a missing address, and that branch returns the wallet sentence atsrc/routes/messages.ts:2824. A forum note with no Lightning address says "This message cannot be paid yet" atsrc/routes/messages.ts:2873. A habit comment has no Nostr event, so that sentence does not fit.src/routes/member-habits.ts:486uses the wallet sentence when the comment author has no Lightning address, and the status stays 409 because 400 on this route is the amount error.src/routes/members.ts:133{ error: 'Not found' }— a non-UUID id is missing, not a sentence of its own.deleteCommentandinvoiceuse that same response.src/index.ts:131contactStorepassed from boot intocreateApp—src/index.ts:132passesmemberHabitStorethe same way.src/routes/messages.ts:2060Invalid body— a JSON body that is not the shape that route accepts.src/routes/member-habits.ts:295uses that sentence when the habit body is not one of the habit actions.src/routes/pos.ts:94Lightning Address could not be resolved— status 502 when that address does not yield an invoice.src/routes/member-habits.ts:502uses that sentence when the habit invoice cannot be built.src/routes/me.ts:934uses the same sentence at status 400 while saving an account address, which is a different job.src/lib/daily-roster.ts:17Invalid comment— the spend roster refuses a comment it will not store.src/routes/member-habits.ts:417uses those words when a habit comment is empty or longer than 2000 code points. A habit comment is not a roster row.Added:
src/routes/member-habits.ts:303Invalid time zone— add stores an IANA zone and refuses a missing or invalid one. Sunday rest cannot do that job, because an invalid zone there means not Sunday.src/routes/member-habits.ts:138Invalid name— the habit title is empty or longer than 80 Unicode code points after trim.src/routes/me.ts:404Name must be 1–80 charactersrejects the account display name onPOST /me/name. This route does not change that name, so the account sentence is a different job.src/routes/member-habits.ts:307returns this sentence on add, andsrc/routes/member-habits.ts:330returns it on edit.src/routes/member-habits.ts:141Invalid description— the public habit description is longer than 2000 code points. No existing sentence checks a habit description.src/routes/member-habits.ts:144Invalid notes— the private notes are longer than 2000 code points. No existing sentence checks habit notes.src/routes/member-habits.ts:272Habits are unavailable— the habit store failed. No existing sentence reports that.src/routes/member-habits.ts:375Invalid status— a log status is notachieved,partial, ormissed. No existing sentence checks a habit log.src/routes/member-habits.ts:383Invalid period— the period is not a real day, or not a Monday when the habit is weekly. No existing sentence checks a habit period.src/routes/member-habits.ts:339Period is closed— an edit whose current period is afterlastPeriod. The stored wording does not change.src/routes/member-habits.ts:399is that same sentence when a log is no longer ratable.src/routes/member-habits.ts:478Cannot donate to yourself— the caller is paying their own habit comment.src/routes/conversations.ts:590Cannot message yourselfrefuses opening a conversation with oneself.src/routes/conversations.ts:1198uses that same sentence when a conversation invoice's counterpart is the caller, because that payment is a message in the thread. A habit comment is not a message and has no thread, so that sentence would tell the donor they failed to message themselves. The donation refusal is a different job.src/routes/member-habits.ts:224app.get—GET /habits. No existing route lists a member habit, its periods, or a comment on that habit.src/routes/member-habits.ts:276app.post—POST /habits. No existing route adds, edits, archives, logs, comments on, or invoices a member habit.