issue 1858: handle, unhandle and remind an item, each with its own line (1858.2)
ADR 0517 §1, §2, §4, §5: an item is a task, a promise or the mail itself; each act and its action line are kept in one transaction, a refused or failed act writes none, an undo lasts until the day ends and a reminder shows the item again the next day (Europe/Brussels). The seat's serves list in mesh-mail's manifest names the new verbs.
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
package main
|
||||
|
||||
// The operator's days (design 63 §2, novox/hq ADR 0517 §1, §2): today, this week and the day an undo ends are
|
||||
// days in the operator's own time zone, Europe/Brussels, whatever the machine's own zone is. The zone's rules
|
||||
// are built into the binary (time/tzdata), so a machine without a zone database answers the same.
|
||||
|
||||
import (
|
||||
"time"
|
||||
_ "time/tzdata"
|
||||
)
|
||||
|
||||
// Brussels is the operator's time zone.
|
||||
var Brussels = mustZone("Europe/Brussels")
|
||||
|
||||
func mustZone(name string) *time.Location {
|
||||
loc, err := time.LoadLocation(name)
|
||||
if err != nil {
|
||||
panic(err) // time/tzdata carries it: never reached
|
||||
}
|
||||
return loc
|
||||
}
|
||||
|
||||
// dayOf is the operator's day of t, as midnight UTC of that date: the same form a date column answers.
|
||||
func dayOf(t time.Time) time.Time {
|
||||
y, m, d := t.In(Brussels).Date()
|
||||
return time.Date(y, m, d, 0, 0, 0, 0, time.UTC)
|
||||
}
|
||||
|
||||
// endOfWeek is the last day of the week of day (a dayOf): its Sunday.
|
||||
func endOfWeek(day time.Time) time.Time {
|
||||
return day.AddDate(0, 0, (7-int(day.Weekday()))%7)
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
package main
|
||||
|
||||
// Issue 1858 (novox/hq ADR 0517 §1, §2, §4, §5): handle, unhandle and remind, item by item, each writing its
|
||||
// one action line in the same transaction as its act; an act that fails writes none.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// callTool calls a tool by its name through Tools(), so a test names a verb before it exists and fails on
|
||||
// that, not on a compile error.
|
||||
func callTool(t *testing.T, b *Bundle, name string, args map[string]any) (any, error) {
|
||||
t.Helper()
|
||||
for _, tool := range b.Tools() {
|
||||
if tool.Name == name {
|
||||
return tool.Run(args)
|
||||
}
|
||||
}
|
||||
t.Fatalf("the bundle serves no %s", name)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// brusselsAt is a time on the operator's own clock (design 63 §2).
|
||||
func brusselsAt(year int, month time.Month, day, hour, minute int) time.Time {
|
||||
return time.Date(year, month, day, hour, minute, 0, 0, Brussels)
|
||||
}
|
||||
|
||||
// listBundle is a bundle over store whose clock reads *now.
|
||||
func listBundle(store Store, now *time.Time) *Bundle {
|
||||
b := testBundle(store, "mesh-mail-web")
|
||||
b.Now = func() time.Time { return *now }
|
||||
return b
|
||||
}
|
||||
|
||||
func lines(t *testing.T, store Store) []ActionLine {
|
||||
t.Helper()
|
||||
out, err := store.Actions(context.Background(), 1000)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// 1858/AC4: handle, unhandle and remind each write exactly one line — what, mail id, item, mode, when — and
|
||||
// change the item's handling.
|
||||
func TestHandleUnhandleAndRemindEachWriteOneLine(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
store := newMemStore()
|
||||
id := entryWithItems(t, store)
|
||||
now := brusselsAt(2026, 10, 22, 9, 15)
|
||||
b := listBundle(store, &now)
|
||||
|
||||
if _, err := callTool(t, b, HandleTool, map[string]any{"id": float64(id), "item": "task 2", "mode": "work"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
hs, _ := store.Handlings(ctx)
|
||||
if len(hs) != 1 || hs[0].Entry != id || hs[0].Item != "task 2" || !hs[0].Handled.Equal(now) {
|
||||
t.Fatalf("handlings = %+v, want task 2 handled at %s", hs, now)
|
||||
}
|
||||
got := lines(t, store)
|
||||
if len(got) != 1 || got[0].What != ActHandled || got[0].Entry != id || got[0].Item != "task 2" || got[0].Mode != "work" || !got[0].At.Equal(now) {
|
||||
t.Fatalf("lines = %+v, want one: handled, %d, task 2, work, %s", got, id, now)
|
||||
}
|
||||
|
||||
now = now.Add(time.Minute)
|
||||
if _, err := callTool(t, b, UnhandleTool, map[string]any{"id": float64(id), "item": "task 2", "mode": "work"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if hs, _ := store.Handlings(ctx); len(hs) != 0 {
|
||||
t.Fatalf("handlings after unhandle = %+v, want none", hs)
|
||||
}
|
||||
if got := lines(t, store); len(got) != 2 || got[0].What != ActUndone || got[0].Item != "task 2" {
|
||||
t.Fatalf("lines = %+v, want undone newest", got)
|
||||
}
|
||||
|
||||
if _, err := callTool(t, b, RemindTool, map[string]any{"id": float64(id), "mode": "personal"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
hs, _ = store.Handlings(ctx)
|
||||
tomorrow := time.Date(2026, 10, 23, 0, 0, 0, 0, time.UTC)
|
||||
if len(hs) != 1 || hs[0].Item != "mail" || !hs[0].ShowsAgain.Equal(tomorrow) || !hs[0].Handled.IsZero() {
|
||||
t.Fatalf("handlings after remind = %+v, want the mail shown again on 2026-10-23", hs)
|
||||
}
|
||||
if got := lines(t, store); len(got) != 3 || got[0].What != ActReminded || got[0].Item != "mail" || got[0].Mode != "personal" {
|
||||
t.Fatalf("lines = %+v, want reminded newest", got)
|
||||
}
|
||||
}
|
||||
|
||||
// 1858/AC4: an act that fails — refused, or failing in the store — writes no line.
|
||||
func TestAFailedActWritesNoLine(t *testing.T) {
|
||||
store := newMemStore()
|
||||
id := entryWithItems(t, store)
|
||||
now := brusselsAt(2026, 10, 22, 9, 15)
|
||||
b := listBundle(store, &now)
|
||||
for _, tc := range []struct {
|
||||
name, verb string
|
||||
args map[string]any
|
||||
why string
|
||||
}{
|
||||
{"no such item", HandleTool, map[string]any{"id": float64(id), "item": "task 3"}, "task 3"},
|
||||
{"no such entry", HandleTool, map[string]any{"id": float64(id + 99)}, "no mail entry"},
|
||||
{"not an item", RemindTool, map[string]any{"id": float64(id), "item": "the subject"}, "item"},
|
||||
{"not a mode", HandleTool, map[string]any{"id": float64(id), "mode": "holiday"}, "mode"},
|
||||
{"undo what is not handled", UnhandleTool, map[string]any{"id": float64(id), "item": "promise 1"}, "not handled"},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
_, err := callTool(t, b, tc.verb, tc.args)
|
||||
if err == nil || !strings.Contains(err.Error(), tc.why) {
|
||||
t.Fatalf("%s %v: %v, want a refusal naming %q", tc.verb, tc.args, err, tc.why)
|
||||
}
|
||||
if got := lines(t, store); len(got) != 0 {
|
||||
t.Fatalf("lines = %+v, want none", got)
|
||||
}
|
||||
})
|
||||
}
|
||||
store.failAct = errStoreFake
|
||||
if _, err := callTool(t, b, HandleTool, map[string]any{"id": float64(id)}); err == nil {
|
||||
t.Fatal("handle with a failing store: no error")
|
||||
}
|
||||
if hs, _ := store.Handlings(context.Background()); len(hs) != 0 || len(lines(t, store)) != 0 {
|
||||
t.Fatalf("a failed act kept %+v and wrote %+v", hs, lines(t, store))
|
||||
}
|
||||
}
|
||||
|
||||
// 1858/AC2 (undo lasts the day): an item handled yesterday is not undone today.
|
||||
func TestUndoLastsUntilTheDayEnds(t *testing.T) {
|
||||
store := newMemStore()
|
||||
id := entryWithItems(t, store)
|
||||
now := brusselsAt(2026, 10, 22, 23, 50)
|
||||
b := listBundle(store, &now)
|
||||
if _, err := callTool(t, b, HandleTool, map[string]any{"id": float64(id), "item": "task 1"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
now = brusselsAt(2026, 10, 23, 0, 5)
|
||||
if _, err := callTool(t, b, UnhandleTool, map[string]any{"id": float64(id), "item": "task 1"}); err == nil ||
|
||||
!strings.Contains(err.Error(), "until the day ends") {
|
||||
t.Fatalf("undo the next day: %v, want a refusal saying undo lasts until the day ends", err)
|
||||
}
|
||||
if got := lines(t, store); len(got) != 1 {
|
||||
t.Fatalf("lines = %+v, want the handled line alone", got)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
package main
|
||||
|
||||
// The operator's list and what the operator does with it (novox/hq ADR 0517 §1 to §5, issue 1858): handle,
|
||||
// unhandle and remind an item — a task, a promise, or the mail itself — each writing its one action line in
|
||||
// the same transaction as its act (Store.Act); list, the operator's list for a mode (list.go); record, a line
|
||||
// for an act that changes no entry; and actions, the action log. Each answers only a caller the setting
|
||||
// callers names, like every mail-reading verb. A refused act writes nothing.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ItemMail is the item that is the mail itself; "task n" and "promise n" are its tasks and promises.
|
||||
const ItemMail = "mail"
|
||||
|
||||
// Modes are the page's modes (ADR 0515 §4): which accounts each shows is mesh-mail-web's setting `modes`.
|
||||
var Modes = []string{"work", "personal", "everything"}
|
||||
|
||||
// itemArg reads the item an act concerns: the mail itself when left out.
|
||||
func itemArg(args map[string]any) (string, error) {
|
||||
v, has := args["item"]
|
||||
if !has || v == nil {
|
||||
return ItemMail, nil
|
||||
}
|
||||
s, _ := v.(string)
|
||||
for _, known := range []string{ItemMail, "task 1", "task 2", "task 3", "promise 1", "promise 2", "promise 3"} {
|
||||
if s == known {
|
||||
return s, nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("the item %q is none of mail, task 1 to 3, promise 1 to 3", s)
|
||||
}
|
||||
|
||||
// modeArg reads the mode the page is in: everything when left out.
|
||||
func modeArg(args map[string]any) (string, error) {
|
||||
v, has := args["mode"]
|
||||
if !has || v == nil {
|
||||
return "everything", nil
|
||||
}
|
||||
s, _ := v.(string)
|
||||
for _, known := range Modes {
|
||||
if s == known {
|
||||
return s, nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("the mode %q is none of work, personal, everything", s)
|
||||
}
|
||||
|
||||
// holdsItem says whether the entry holds the item: the mail always, a task or a promise by its number.
|
||||
func holdsItem(e Entry, item string) bool {
|
||||
if item == ItemMail {
|
||||
return true
|
||||
}
|
||||
var n int
|
||||
if _, err := fmt.Sscanf(item, "task %d", &n); err == nil {
|
||||
return hasTask(e, n)
|
||||
}
|
||||
if _, err := fmt.Sscanf(item, "promise %d", &n); err == nil {
|
||||
return hasPromise(e, n)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// itemAct is what handle, unhandle and remind share: the caller checked, the arguments read, the entry and
|
||||
// the item found, and the item's handling as it stands.
|
||||
type itemAct struct {
|
||||
store Store
|
||||
entry Entry
|
||||
item string
|
||||
mode string
|
||||
current Handling
|
||||
}
|
||||
|
||||
func (b *Bundle) startItemAct(verb string, args map[string]any) (itemAct, error) {
|
||||
if _, err := b.checkCaller(args); err != nil {
|
||||
return itemAct{}, err
|
||||
}
|
||||
if err := only(args, "id", "item", "mode"); err != nil {
|
||||
return itemAct{}, err
|
||||
}
|
||||
id, err := number(args, "id", 0)
|
||||
if err != nil {
|
||||
return itemAct{}, err
|
||||
}
|
||||
if id <= 0 {
|
||||
return itemAct{}, fmt.Errorf("%s needs id", verb)
|
||||
}
|
||||
item, err := itemArg(args)
|
||||
if err != nil {
|
||||
return itemAct{}, err
|
||||
}
|
||||
mode, err := modeArg(args)
|
||||
if err != nil {
|
||||
return itemAct{}, err
|
||||
}
|
||||
ctx := context.Background()
|
||||
store, err := b.Store(ctx)
|
||||
if err != nil {
|
||||
return itemAct{}, err
|
||||
}
|
||||
entry, ok, err := store.Entry(ctx, int64(id))
|
||||
if err != nil {
|
||||
return itemAct{}, err
|
||||
}
|
||||
if !ok {
|
||||
return itemAct{}, fmt.Errorf("%s: no mail entry %d is held; list answers the ids held", verb, id)
|
||||
}
|
||||
if !holdsItem(entry, item) {
|
||||
return itemAct{}, fmt.Errorf("%s: the mail entry %d holds no %s", verb, id, item)
|
||||
}
|
||||
a := itemAct{store: store, entry: entry, item: item, mode: mode, current: Handling{Entry: entry.ID, Item: item}}
|
||||
handlings, err := store.Handlings(ctx)
|
||||
if err != nil {
|
||||
return itemAct{}, err
|
||||
}
|
||||
for _, h := range handlings {
|
||||
if h.Entry == entry.ID && h.Item == item {
|
||||
a.current = h
|
||||
}
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// act keeps the item's new handling and the act's line, in one transaction (Store.Act).
|
||||
func (a itemAct) act(verb, what string, h Handling, at time.Time) (any, error) {
|
||||
err := a.store.Act(context.Background(), h, ActionLine{What: what, Entry: a.entry.ID, Item: a.item, Mode: a.mode, At: at})
|
||||
if err == ErrNotFound {
|
||||
return nil, fmt.Errorf("%s: no mail entry %d is held; list answers the ids held", verb, a.entry.ID)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return map[string]any{what: map[string]any{"id": a.entry.ID, "item": a.item}}, nil
|
||||
}
|
||||
|
||||
// handleTool marks an item handled (ADR 0517 §1): the mail itself handles all its items with it.
|
||||
func (b *Bundle) handleTool(args map[string]any) (any, error) {
|
||||
a, err := b.startItemAct(HandleTool, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !a.current.Handled.IsZero() {
|
||||
return nil, fmt.Errorf("%s: %s of mail entry %d is handled already", HandleTool, a.item, a.entry.ID)
|
||||
}
|
||||
now := b.now()
|
||||
h := a.current
|
||||
h.Handled = now
|
||||
return a.act(HandleTool, ActHandled, h, now)
|
||||
}
|
||||
|
||||
// unhandleTool undoes a handling of today (ADR 0517 §1: the undo lasts until the day ends).
|
||||
func (b *Bundle) unhandleTool(args map[string]any) (any, error) {
|
||||
a, err := b.startItemAct(UnhandleTool, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
now := b.now()
|
||||
if a.current.Handled.IsZero() {
|
||||
return nil, fmt.Errorf("%s: %s of mail entry %d is not handled", UnhandleTool, a.item, a.entry.ID)
|
||||
}
|
||||
if !dayOf(a.current.Handled).Equal(dayOf(now)) {
|
||||
return nil, fmt.Errorf("%s: %s of mail entry %d was handled before today, and an undo lasts until the day ends",
|
||||
UnhandleTool, a.item, a.entry.ID)
|
||||
}
|
||||
h := a.current
|
||||
h.Handled = time.Time{}
|
||||
return a.act(UnhandleTool, ActUndone, h, now)
|
||||
}
|
||||
|
||||
// remindTool hides an item until the next day, when the list shows it under today (ADR 0517 §2).
|
||||
func (b *Bundle) remindTool(args map[string]any) (any, error) {
|
||||
a, err := b.startItemAct(RemindTool, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !a.current.Handled.IsZero() {
|
||||
return nil, fmt.Errorf("%s: %s of mail entry %d is handled; undo it first", RemindTool, a.item, a.entry.ID)
|
||||
}
|
||||
now := b.now()
|
||||
h := a.current
|
||||
h.ShowsAgain = dayOf(now).AddDate(0, 0, 1)
|
||||
return a.act(RemindTool, ActReminded, h, now)
|
||||
}
|
||||
@@ -59,7 +59,7 @@ func TestTheManifestClaimsMailReadingAndInvokesReadOnly(t *testing.T) {
|
||||
t.Fatalf("runs-as %q, want %q", m.RunsAs, Module)
|
||||
}
|
||||
wantClaims := []seat{
|
||||
{Name: "mail-reading", Scope: "mesh", Serves: []string{"entries", "groups", "correct", "lessons", "undo", "status"}},
|
||||
{Name: "mail-reading", Scope: "mesh", Serves: []string{"entries", "groups", "correct", "lessons", "undo", "status", "handle", "unhandle", "remind"}},
|
||||
}
|
||||
if !reflect.DeepEqual(m.Claims, wantClaims) {
|
||||
t.Fatalf("claims %+v, want %+v", m.Claims, wantClaims)
|
||||
|
||||
@@ -29,6 +29,14 @@ const (
|
||||
LessonsTool = "mail-reading.lessons"
|
||||
UndoTool = "mail-reading.undo"
|
||||
StatusTool = "mail-reading.status"
|
||||
|
||||
// The verbs of the operator's list (novox/hq ADR 0517 §4, issue 1858).
|
||||
ListTool = "mail-reading.list"
|
||||
HandleTool = "mail-reading.handle"
|
||||
UnhandleTool = "mail-reading.unhandle"
|
||||
RemindTool = "mail-reading.remind"
|
||||
RecordTool = "mail-reading.record"
|
||||
ActionsTool = "mail-reading.actions"
|
||||
)
|
||||
|
||||
// Bundle is what the tools reach: the trusted setting `callers`, the runtime's own caller, and the index
|
||||
@@ -38,6 +46,7 @@ type Bundle struct {
|
||||
Env Env
|
||||
Caller func(args map[string]any) string
|
||||
Store func(ctx context.Context) (Store, error)
|
||||
Now func() time.Time // nil outside a test: time.Now
|
||||
|
||||
// callersOverride, when set, is used instead of reading Env.Callers — the tests' own seam.
|
||||
callersOverride func() (Callers, error)
|
||||
@@ -48,6 +57,13 @@ type Env struct {
|
||||
Callers string // the path MESH_MAIL_READER_CALLERS names.
|
||||
}
|
||||
|
||||
func (b *Bundle) now() time.Time {
|
||||
if b.Now != nil {
|
||||
return b.Now()
|
||||
}
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
func (b *Bundle) caller(args map[string]any) string {
|
||||
if b.Caller != nil {
|
||||
return b.Caller(args)
|
||||
@@ -180,6 +196,25 @@ func (b *Bundle) Tools() []stdio.Tool {
|
||||
"no mail read says so). Answered only to a caller the setting callers names. Read-only.",
|
||||
Input: schema(nil, map[string]any{}),
|
||||
Run: b.statusTool},
|
||||
{Name: HandleTool,
|
||||
Description: "Mark one item of a mail entry handled for the operator (novox/hq ADR 0517 §1): a task or a " +
|
||||
"promise by its number, or the mail itself, which handles all its items with it. It changes nothing " +
|
||||
"in the mailbox, and writes one handled line in the action log. Answered only to a caller the " +
|
||||
"setting callers names.",
|
||||
Input: itemActSchema(),
|
||||
Run: b.handleTool},
|
||||
{Name: UnhandleTool,
|
||||
Description: "Undo a handling of today (novox/hq ADR 0517 §1): the item is back in its section; the undo " +
|
||||
"lasts until the day ends (Europe/Brussels). Writes one undone line. Answered only to a caller the " +
|
||||
"setting callers names.",
|
||||
Input: itemActSchema(),
|
||||
Run: b.unhandleTool},
|
||||
{Name: RemindTool,
|
||||
Description: "Remind me tomorrow (novox/hq ADR 0517 §2): the item is hidden until the next day, when the " +
|
||||
"list shows it under today. Writes one reminded line. Answered only to a caller the setting callers " +
|
||||
"names.",
|
||||
Input: itemActSchema(),
|
||||
Run: b.remindTool},
|
||||
{Name: HealthTool,
|
||||
Description: "How mesh-mail-reader is (novox/hq ADR 0459 §12): unhealthy once a batch judgement is " +
|
||||
"refused, times out or answers out of shape, naming which and since when; healthy once one " +
|
||||
@@ -189,6 +224,15 @@ func (b *Bundle) Tools() []stdio.Tool {
|
||||
}
|
||||
}
|
||||
|
||||
// itemActSchema is handle's, unhandle's and remind's own arguments.
|
||||
func itemActSchema() map[string]any {
|
||||
return schema([]string{"id"}, map[string]any{
|
||||
"id": integer("the mail entry's id, as list answered it"),
|
||||
"item": text("task 1 to 3 or promise 1 to 3 by its number, or mail (the mail itself, also when left out)"),
|
||||
"mode": text("the mode the page is in: work, personal or everything (when left out)"),
|
||||
})
|
||||
}
|
||||
|
||||
// entryOut is one mail entry as entriesTool answers it (1521/AC1): account, message id, date, sender name,
|
||||
// subject, group (by its label), summary, mark, why and due date — never a mail body.
|
||||
func entryOut(e Entry, labels map[int64]string) map[string]any {
|
||||
|
||||
@@ -22,7 +22,8 @@ func TestToolsListsTheSixVerbsAndHealth(t *testing.T) {
|
||||
}
|
||||
want := []string{
|
||||
"mail-reading.entries", "mail-reading.groups", "mail-reading.correct", "mail-reading.lessons",
|
||||
"mail-reading.undo", "mail-reading.status", "mail_reader_health",
|
||||
"mail-reading.undo", "mail-reading.status", "mail-reading.handle", "mail-reading.unhandle", "mail-reading.remind",
|
||||
"mail_reader_health",
|
||||
}
|
||||
if len(names) != len(want) {
|
||||
t.Fatalf("Tools() = %v, want %v", names, want)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
{
|
||||
"name": "mail-reading",
|
||||
"scope": "mesh",
|
||||
"serves": ["entries", "groups", "correct", "lessons", "undo", "status"]
|
||||
"serves": ["entries", "groups", "correct", "lessons", "undo", "status", "handle", "unhandle", "remind"]
|
||||
}
|
||||
],
|
||||
"consumes": [
|
||||
@@ -48,6 +48,9 @@
|
||||
"mail-reading.lessons",
|
||||
"mail-reading.undo",
|
||||
"mail-reading.status",
|
||||
"mail-reading.handle",
|
||||
"mail-reading.unhandle",
|
||||
"mail-reading.remind",
|
||||
"mail_reader_health"
|
||||
],
|
||||
"runtime-health": {
|
||||
|
||||
@@ -148,7 +148,7 @@ func TestTheManifestSaysWhatTheCodeDoes(t *testing.T) {
|
||||
{Name: "mail-store", Scope: "mesh", Serves: []string{"accounts", "search", "read", "send", "send-status"}},
|
||||
{Name: "mail-webclient", Scope: "mesh", Serves: []string{"open", "status"}},
|
||||
{Name: "mail-notifier", Scope: "node", Serves: []string{"status"}, Needs: []string{"node-notifier"}},
|
||||
{Name: "mail-reading", Scope: "mesh", Serves: []string{"entries", "groups", "correct", "lessons", "undo", "status"}},
|
||||
{Name: "mail-reading", Scope: "mesh", Serves: []string{"entries", "groups", "correct", "lessons", "undo", "status", "handle", "unhandle", "remind"}},
|
||||
}
|
||||
if !reflect.DeepEqual(m.Seats, wantSeats) {
|
||||
t.Fatalf("seats %+v, want %+v", m.Seats, wantSeats)
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
{
|
||||
"name": "mail-reading",
|
||||
"scope": "mesh",
|
||||
"serves": ["entries", "groups", "correct", "lessons", "undo", "status"]
|
||||
"serves": ["entries", "groups", "correct", "lessons", "undo", "status", "handle", "unhandle", "remind"]
|
||||
}
|
||||
],
|
||||
"claims": [
|
||||
|
||||
Reference in New Issue
Block a user