Pin at the store that a parsed book has no provenance yet, and declare the two new rows that carry alarm markers
This commit is contained in:
parent
3435bd6eee
commit
3524e47d4b
6 changed files with 195 additions and 17 deletions
File diff suppressed because one or more lines are too long
|
|
@ -349,6 +349,58 @@ r.ordered_units is not null then` — считают полосу в ЮНИТА
|
|||
разойтись», который этот пак вычищал из денежного пути. Вынесен в `makeRecipe(t, target)`, оба гейта
|
||||
читают один разбор; прежний тест зелен без правок.
|
||||
|
||||
### ⛔ ТРЕТИЙ ЗАХОД ПО НОРМЕ АВТОРСТВА — ОДНА ЭРА, ТРИ НОСИТЕЛЯ, ПРОТУХШИЕ ПО-РАЗНОМУ
|
||||
|
||||
Найдено не зоной: строитель нашёл, оркестратор проверил, зона пишет из пина (`D39.211` п.3а).
|
||||
|
||||
**⑴ `Book.structure` и `OrderOptions.structure` несли РАЗНОЕ, хотя канон утверждал обратное.**
|
||||
В `OrderOptions.structure` я записала «the sibling `Book.structure` carries the same fact and says the
|
||||
same in its own words; **the two cannot disagree**». Проверено — на тот момент это было заявление о
|
||||
НАМЕРЕНИИ, а не о тексте: `Book.structure` говорил только «`null` while the book is still arriving» и
|
||||
не различал `null` и `none` ВОВСЕ. ⇒ правило «нет провенанса ≠ есть, и он плохой» жило в схеме ЗАКАЗА,
|
||||
а поле, которое клиент видит на карточке КНИГИ, о нём молчало.
|
||||
|
||||
**⑵ И «still arriving» ýже факта.** Книга может приехать целиком и отвечать `null`, потому что разрез
|
||||
не гоняли. Это не рассуждение: пин, на котором стоит правило, берёт книгу в статусе `not_started` —
|
||||
полностью загруженную, без манифеста — и получает явный `null`.
|
||||
|
||||
**⑶ Третий носитель той же эры — МОЙ КОД.** `httpapi.wireBook.Structure` перечислял ТРИ значения
|
||||
(`declared`/`detected`/`none`) и `delimited` не знал: комментарий остался от трёхзначной эры и читался
|
||||
как обещание. ⇒ **три носителя одной эры протухли по-разному, и один из них мой.** Перечень из
|
||||
комментария УБРАН совсем: словарь открытый и растёт аддитивно, список в комментарии протухает молча —
|
||||
канон держит слова, комментарий держит форму.
|
||||
|
||||
**ПИН ВЫДЕЛЕН, А НЕ ПРОЦИТИРОВАН ЧУЖОЙ.** Утверждение о `null` на карточке книги держалось внутри
|
||||
`TestTheCharacterCountSaysWhichNumberItIs` — постояльцем без своего имени, а канон, цитирующий пин по
|
||||
имени, не может цитировать пин, названный про другое. Перенесено (не скопировано) в
|
||||
`httpapi.TestABooksCutProvenanceIsTheEnginesOwnWordOrAnExplicitNull`, туда же добавлено третье
|
||||
утверждение: незнакомое слово (`delimited`) едет ВЕРБАТИМ, потому что сборка, переписывающая
|
||||
непонятое, спрятала бы рост словаря от клиента, который обязан на нём деградировать.
|
||||
|
||||
⚠ **И правило, которое зона взяла себе из этого:** смысл значения нельзя списывать с комментария
|
||||
провода — сегодня это ВТОРОЙ случай, когда так списанное утверждение оказалось ложным. Источник —
|
||||
ПИН.
|
||||
|
||||
⛔ **И ТУТ ЖЕ — ЧЕТВЁРТЫЙ СЛУЧАЙ, ТОНЬШЕ ВСЕХ ПРЕДЫДУЩИХ: ПИН БЫЛ, А УТВЕРЖДЕНИЕ ВСЁ РАВНО УЕХАЛО.**
|
||||
Я написала в канон, что `null` бывает у книги «fully uploaded, no manifest», и сослалась на пин, чей
|
||||
фейк отдаёт `not_started` без структуры. Пин настоящий и держит РОВНО ТО, ЧТО ДЕРЖИТ: пустая колонка
|
||||
доезжает до клиента явным `null`. Но это **тест ОТОБРАЖЕНИЯ** — он ничего не говорит о том, бывает ли
|
||||
такая пара у настоящей книги. Я прочитала ФИКСТУРУ как продуктовое состояние. Поймал оркестратор
|
||||
сверкой с соседним словарём: `BookStatus` обещает у `not_started` «cut», то есть манифест прочитан.
|
||||
|
||||
⇒ **Ответ дал пин уровня ХРАНИЛИЩА, заведённый под этот вопрос** —
|
||||
`pgstore.TestAParsedBookIsNotStartedBeforeItHasAnyProvenance`. `FinishParse` ставит `not_started` и
|
||||
ВЗВОДИТ ДОЛГ НА ДЕРЕВО в одной транзакции (его собственный комментарий: «this is the transaction that
|
||||
makes the book parsed, and a book that is parsed owes a tree»). ⇒ окно «разрезана, провенанса ещё нет»
|
||||
СПРОЕКТИРОВАНО, а не гонка. Ложной оказалась **моя иллюстрация**, а не словарь статусов и не
|
||||
различение `null`/`none`: `not_started` говорит, что разрезан ИСХОДНИК, а не что прочитан манифест.
|
||||
Три дороги к тому же `null`: долг ещё не оплачен · манифест старше поля · книга заведена с уже
|
||||
лежащего файла.
|
||||
|
||||
⚠ **Урок, который стоит отдельно от находки:** «есть пин» — не то же, что «пин доказывает сказанное».
|
||||
Доказано было ОТОБРАЖЕНИЕ, сказано — про ЖИЗНЬ. Цитируя пин в утверждении о значении, спрашивать не
|
||||
«зелен ли он», а «то ли он мерит».
|
||||
|
||||
### ⛔ ТО ЖЕ РАСЩЕПЛЕНИЕ В СОСЕДНИХ ПОЛЯХ — ПРОВЕРЕНО МЕХАНИЧЕСКИ, НЕ ГЛАЗАМИ
|
||||
|
||||
Заказ оркестратора: «проверь заодно, нет ли того же расщепления в соседних полях». Сделан сканер по
|
||||
|
|
@ -786,16 +838,33 @@ ordered_units: integer|null // НОВОЕ: сколько юнитов ку
|
|||
make check → MAKE-EXIT=0 ⟵ ПЕРЕ-СНЯТО 06.09 ПОСЛЕ ДОФИКСА ПО КАНОНУ, красных НЕТ
|
||||
golangci-lint: 0 issues · gofmt чист · go vet чист · sqlc diff чист
|
||||
ПАКЕТОВ 20: ok 20, FAIL 0
|
||||
ТЕСТОВ верхнеуровневых 852: PASS 847 · FAIL 0 · SKIP 5
|
||||
(847-й — новый гейт прибора, `TestTheBatteryCannotReportCleanlinessWithoutItsLog`)
|
||||
⚠ ИСПР.: прежняя редакция этого блока писала «ПАКЕТОВ 21». Их ДВАДЦАТЬ. Двадцать первым
|
||||
я посчитала лишнюю строку `FAIL` в выводе `make` — он печатает и падение ПАКЕТА, и
|
||||
итоговое голое `FAIL`. Ошибка ровно того же рода, что ловил четвёртый заход: величина
|
||||
взята из вывода прибора, не разобрав, что прибор печатает.
|
||||
ТЕСТОВ верхнеуровневых 854: PASS 849 · FAIL 0 · SKIP 5
|
||||
РЕГИСТР: 457 рядов · открытых 105 · major 3 · minor 35 · info 67
|
||||
(847-й — гейт прибора `TestTheBatteryCannotReportCleanlinessWithoutItsLog`; 848-й —
|
||||
выделенный пин провенанса `TestABooksCutProvenanceIsTheEnginesOwnWordOrAnExplicitNull`;
|
||||
849-й — пин ЖИЗНЕННОГО состояния `pgstore.TestAParsedBookIsNotStartedBeforeItHasAnyProvenance`,
|
||||
заведённый потому, что прежний доказывал ОТОБРАЖЕНИЕ, а канон говорил про ЖИЗНЬ)
|
||||
⚠ ИСПР.: прежняя редакция этого блока писала «ПАКЕТОВ 21». Их ДВАДЦАТЬ.
|
||||
⛔ И ОБЪЯСНЕНИЕ, которое здесь стояло («двадцать первым я посчитала лишнюю голую
|
||||
строку `FAIL`»), тоже НЕ ВЫДЕРЖАЛО ПРОВЕРКИ — это была третья догадка подряд об одном
|
||||
числе. Пере-считано по сохранённым выводам: зелёный прогон даёт `ok` 20, `FAIL` 0 —
|
||||
лишней строки нет ВОВСЕ; красный даёт `ok` 19 + `FAIL <пакет>` 1 + голых `FAIL` ДВА,
|
||||
итого 22 строки при тех же 20 пакетах. ⇒ «21» получилось СЛОЖЕНИЕМ ДВУХ РАЗНЫХ
|
||||
ПРОГОНОВ: `ok 20` взято из зелёного, `FAIL 1` — из красного. ⚠ **ИСПР. ПОВТОРНО:** здесь стояло «три
|
||||
наблюдателя, три причины ОДНОГО числа». Неверно, и снято по замеру оркестратора: у него
|
||||
`grep -c '^ok.*internal/money'` = **2** в одном логе и 1 после починки — дубль реален и
|
||||
объясняет ЕГО двадцать один. **Чисел было ДВА, а не одно:** его — от дубля в общем логе,
|
||||
моё — от сложения двух прогонов. Общего у них только величина. ⚠ Формулировка была
|
||||
красивее правды и потому прожила дольше неё — ровно то, за чем этот отчёт весь день
|
||||
гоняется.
|
||||
⚠ счёт снят из `.check.log` (`make check` гонит `-v`); `go test` БЕЗ `-v` строк
|
||||
`--- SKIP` не печатает вовсе, и греп по нему даёт ЛОЖНЫЙ НОЛЬ — этой ошибкой
|
||||
четвёртый заход уже был пойман
|
||||
ALARM PD-count: 12 (baseline 12) — ПЕРЕ-СНЯТА базой: PD-375 и PD-422 удалены из `alarmBaseline`
|
||||
ALARM PD-count: 14 (baseline 14) — ⚠ ЧИСЛО ВЫРОСЛО 06.09 НЕ ОТ НОВОЙ ОПАСНОСТИ, а от того, что две
|
||||
находки пака наконец получили РЯДЫ: `PD-455` и `PD-456` объявлены в `alarmBaseline` с
|
||||
доводом, по прямой инструкции самого гейта. Маркеры они несут законно (холд, деньги,
|
||||
молчание), ниже major стоят потому, что ни одна не теряет денег и не портит работы —
|
||||
ломается продукт. Прежде: 12 (baseline 12), ПЕРЕ-СНЯТА базой: PD-375 и PD-422 удалены из `alarmBaseline`
|
||||
по прямой инструкции самого гейта («ОБА ОБЯЗАНЫ ПОКИНУТЬ КЛАСС НА ЛЕНДИНГЕ») и
|
||||
прецеденту PD-168; было 14 (baseline 14) с двумя объявленными исключениями
|
||||
|
||||
|
|
@ -1051,7 +1120,13 @@ status` на входе сессии — clean). В него ВХОДИТ `81a89
|
|||
мутациями (`SourceChars: 0` красит оба, потеря `BookOnce` — первый); ⑸ форма над книгой с ЖИВЫМ
|
||||
прогоном отвечает `covers_all` и молчит о том, что клик получит `run_in_flight`; ⑹ глава
|
||||
ЗАДРАФЧЕННАЯ, но не отредактированная, оценивается полностью (счётчики стадию не различают),
|
||||
ошибка ВВЕРХ; ⑺ откат миграции стирает различение двух причин паузы, ради которого пак их и
|
||||
ошибка ВВЕРХ ⟵ ⑵/⑸/⑹ **ПОЛУЧИЛИ РЯДЫ 06.09** (`PD-457`/`PD-455`/`PD-456`): до этого их
|
||||
единственным носителем был ЭТОТ отчёт, а отчёт — хроника, и находка без ряда не находится.
|
||||
⚠ **⑶ РЯДА НЕ ПОЛУЧИЛ И НЕ ДОЛЖЕН, но назван здесь строкой по просьбе оркестратора:** новые фикстуры
|
||||
пишут колонки `pgstore` рукописным SQL ИЗ пакета `internal/runs`, куда не достаёт ни `sqlgate`
|
||||
(читает свой каталог), ни `sqlcgate`. Предмет — ДЫРА В ПОКРЫТИИ ГЕЙТОВ, а не дефект поведения:
|
||||
сегодня ни один сторож не судит эти вставки, и через месяц об этом никто не вспомнит. Кандидат в
|
||||
расширение области `sqlgate`, не в ряд регистра; ⑺ откат миграции стирает различение двух причин паузы, ради которого пак их и
|
||||
разделил — для отката приемлемо, но записано.
|
||||
- **PLAUSIBLE, не проверено исполнением:** что `--resnapshot` на каждом продолжении не приводит к
|
||||
заметной пере-оплате на РЕАЛЬНОЙ книге с растущим банком. Механизм я разобрала по коду движка и
|
||||
|
|
@ -4273,7 +4348,7 @@ caught:` того теста, который она обязана валить
|
|||
вне идемпотентности ключа запроса); повтор после успеха отвечает `run_in_flight` либо
|
||||
`ErrRePassUnavailable` — факт погашен финишем (символ `ErrRePassUnavailable`:
|
||||
`platform/internal/runs/runs.go:149`=`var ErrRePassUnavailable = errors.New(`; ветка провода —
|
||||
`platform/internal/httpapi/v0.go:1149`=`case errors.Is(err, runs.ErrRePassUnavailable):`).
|
||||
`platform/internal/httpapi/v0.go:1158`=`case errors.Is(err, runs.ErrRePassUnavailable):`).
|
||||
Вырожденных дублей не нашёл, но специального пина нет.
|
||||
- ⚠ **Для оркестратора — находка опровергателя P10, носителя ни в регистре, ни в бэклоге у неё нет:**
|
||||
якоря §2.12 компаньона контракта (`docs/architecture/14-api-contract/README.md`, греп
|
||||
|
|
|
|||
|
|
@ -73,6 +73,19 @@ var alarmBaseline = []string{
|
|||
// ровно один) и о том, что отказ приходит МОЛЧА для пользователя, дважды нажавшего кнопку.
|
||||
// Ниже major он стоит потому, что деньги не теряются: отказ случается ДО взятия холда.
|
||||
"PD-448",
|
||||
// PD-455 — вошёл в класс 06.09 вторым кругом пака «форма заказа». Маркеры законны: ряд про то, что
|
||||
// форма МОЛЧИТ о живом прогоне, и про ХОЛД, потому что отказ случается ровно до его взятия. Ниже
|
||||
// major он стоит по той же причине, по которой его цена терпима: денег не теряется и работа не
|
||||
// портится — ломается обещание «вердикт ДО клика», то есть продукт, а не деньги.
|
||||
"PD-455",
|
||||
// PD-456 — вошёл в класс 06.09 вторым кругом пака «форма заказа». Маркеры несёт ЗАКОННО: ряд про
|
||||
// деньги и про холд, потому что дефект — завышение оценки остатка (юнит, у которого сделан только
|
||||
// черновик, входит в цену обеими волнами). Ниже major он стоит потому, что **ошибка направлена
|
||||
// ВВЕРХ**: денег не теряется и работа не портится, у человека просят зарезервировать больше
|
||||
// нужного. Цена продуктовая — завышенный холд сокращает покупаемое, а на коротком балансе делает
|
||||
// покупку невозможной там, где она возможна. Лечение упирается в манифест движка (остаток по
|
||||
// ВОЛНАМ, а не только по юнитам), то есть в чужую зону, и одной платформой не закрывается.
|
||||
"PD-456",
|
||||
"PD-94", "PD-107", "PD-162", "PD-201", "PD-212",
|
||||
"PD-217", "PD-244", "PD-418", "PD-420", "PD-428", "PD-433",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -209,10 +209,19 @@ type wireBook struct {
|
|||
// destroy a meaning the contract already promises. Form ratified by the orchestrator, act
|
||||
// D39.201 §5(б), on two lawful shapes the zone put up.
|
||||
CharacterCountExact bool `json:"character_count_exact"`
|
||||
// Structure is where the chapter boundaries came from: `declared` (the format drew them),
|
||||
// `detected` (matched in the prose), `none` (the whole book is one chapter). Null until a
|
||||
// manifest has said. A client needs it to know what a chapter NUMBER is worth before it offers an
|
||||
// order phrased in one — see OrderOptions.chapter_orders.
|
||||
// Structure is where the chapter boundaries came from, in the ENGINE's own word, passed through
|
||||
// untouched. A client needs it to know what a chapter NUMBER is worth before it offers an order
|
||||
// phrased in one — see OrderOptions.chapter_orders.
|
||||
//
|
||||
// ⚠ THE VOCABULARY IS OPEN AND GROWS ADDITIVELY, so this comment does not list it: an earlier
|
||||
// edition named three words, the engine shipped a fourth (`delimited`, 06.09), and the list went
|
||||
// stale in place while reading like a promise. The canon holds the words; this holds the shape.
|
||||
//
|
||||
// ⛔ NULL IS NOT A WORD OF THAT VOCABULARY AND NOT `none`. `none` is a book the engine LOOKED at
|
||||
// and found one chapter in — a statement about the book. Null is the absence of any statement:
|
||||
// no manifest has been read. ⚠ That is NOT the same as «still arriving»: a book can be fully here
|
||||
// and still answer null, because the cut has not been run. Pinned by
|
||||
// TestABooksCutProvenanceIsTheEnginesOwnWordOrAnExplicitNull, whose book is `not_started`.
|
||||
Structure *string `json:"structure"`
|
||||
AddedAt time.Time `json:"added_at"`
|
||||
NoteCount int `json:"note_count"`
|
||||
|
|
|
|||
|
|
@ -935,8 +935,21 @@ func TestTheCharacterCountSaysWhichNumberItIs(t *testing.T) {
|
|||
}
|
||||
})
|
||||
}
|
||||
// The cut's provenance travels beside it, verbatim, or as an explicit null when no manifest has
|
||||
// spoken. The client needs it to know what a chapter NUMBER is worth before it offers an order.
|
||||
}
|
||||
|
||||
// ⛔ THE CUT'S PROVENANCE ON THE BOOK CARD, and `null` there means «nobody has been asked», which is a
|
||||
// different fact from every word the vocabulary carries.
|
||||
//
|
||||
// It used to be asserted inside the character-count test, as a tenant with no name of its own — and a
|
||||
// contract that cites a pin by name cannot cite one named for something else. Moved rather than
|
||||
// copied: the assertions are the same, the subject is now findable.
|
||||
//
|
||||
// ⚠ THE BOOK HERE IS `not_started`, WHICH IS THE POINT. It has fully arrived and still answers `null`,
|
||||
// because no manifest has been read — so «null while the book is still arriving» is NARROWER than the
|
||||
// fact and a client that waits for arrival waits for the wrong thing. And `null` is not `none`: `none`
|
||||
// is a book the engine looked at and found one chapter in, which is a statement ABOUT the book;
|
||||
// `null` is the absence of any statement.
|
||||
func TestABooksCutProvenanceIsTheEnginesOwnWordOrAnExplicitNull(t *testing.T) {
|
||||
got := decode(t, call(t, v0Server(t, &fakeLibrary{book: pgstore.Book{ID: "bk_1", Status: "not_started"}}, &fakeRuns{}), "GET", "/v0/books/bk_1", ""))
|
||||
book, _ := got["book"].(map[string]any)
|
||||
if v, ok := book["structure"]; !ok || v != nil {
|
||||
|
|
@ -947,6 +960,13 @@ func TestTheCharacterCountSaysWhichNumberItIs(t *testing.T) {
|
|||
if book["structure"] != "declared" {
|
||||
t.Errorf("structure = %v, want the engine's own word", book["structure"])
|
||||
}
|
||||
// An unknown word travels VERBATIM too: the vocabulary grows additively, and a build that
|
||||
// re-wrote what it did not recognise would hide the growth from the client that must degrade on it.
|
||||
got = decode(t, call(t, v0Server(t, &fakeLibrary{book: pgstore.Book{ID: "bk_1", Status: "not_started", Structure: "delimited"}}, &fakeRuns{}), "GET", "/v0/books/bk_1", ""))
|
||||
book, _ = got["book"].(map[string]any)
|
||||
if book["structure"] != "delimited" {
|
||||
t.Errorf("structure = %v, want the engine's word passed through untouched", book["structure"])
|
||||
}
|
||||
}
|
||||
|
||||
// ⛔ A RETIRED MEMBER IS REFUSED, NOT IGNORED, and the difference is a purchase.
|
||||
|
|
|
|||
|
|
@ -297,3 +297,61 @@ func TestEveryRunTheStoreHandsOutCarriesItsBooksRevision(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ⛔ A BOOK IS `not_started` BEFORE ITS PROVENANCE EXISTS, and the window is DESIGNED rather than a
|
||||
// race. `FinishParse` sets the status and ARMS THE READ-MODEL DEBT in one transaction — its own
|
||||
// comment says why: «this is the transaction that makes the book parsed, and a book that is parsed
|
||||
// owes a tree». The tree, and with it `structure`, arrive later, when the materializer pays that debt.
|
||||
//
|
||||
// ⚠ THIS PIN EXISTS BECAUSE A CONTRACT SENTENCE WAS WRITTEN OFF A DISPLAY TEST. The canon said
|
||||
// `Book.structure` is `null` for a book that is «fully uploaded, no manifest», citing a test whose
|
||||
// fake store returns a `not_started` book with no structure. That test is real and proves what it
|
||||
// proves — an empty column reaches the client as an explicit `null` — but it is a test of RENDERING:
|
||||
// it says nothing about whether a real book ever holds that pair. The illustration was a fixture read
|
||||
// as a product state. Here the pair is produced by the intake itself.
|
||||
//
|
||||
// ⚠ And it is not the only way to reach it: a manifest older than the `structure` field leaves the
|
||||
// column null for good (pgstore.Structure.Structure, «Empty when the engine did not say»), and
|
||||
// AddBook — the route for a file already on disk — creates a book `not_started` with no manifest at
|
||||
// all. Three roads, one pair; the status vocabulary's «cut, never run» is about the SOURCE having
|
||||
// been cut, not about the platform having read a manifest.
|
||||
//
|
||||
// Mutation caught: arming the read-model debt after the status instead of with it; a status written
|
||||
// only once the tree is materialised (the book would then be invisible to its owner while parsing
|
||||
// completed).
|
||||
func TestAParsedBookIsNotStartedBeforeItHasAnyProvenance(t *testing.T) {
|
||||
s, ctx := testDB(t)
|
||||
now := fundedAccount(t, s, ctx, "u1", "10")
|
||||
b := upload(t, s, ctx, "u1", now)
|
||||
if _, err := s.StartParsing(ctx, b.ID, 42, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
claim, err := s.ClaimParse(ctx, b.ID, now, now.Add(-time.Hour))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := s.FinishParse(ctx, b.ID, claim.At, ParsedBook{Chapters: 500, ChunkerVersion: "chunk-1"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, _, err := s.GetBook(ctx, "u1", b.ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got.Status != "not_started" {
|
||||
t.Fatalf("a book whose parse just finished is %q", got.Status)
|
||||
}
|
||||
if got.Structure != "" {
|
||||
t.Fatalf("a book that has not been materialised carries provenance %q: the pair this pin is "+
|
||||
"about does not occur, and the contract's illustration of `null` is wrong in the other "+
|
||||
"direction", got.Structure)
|
||||
}
|
||||
// …and it is not that the book is somehow unfinished: it owes a tree, and the debt is what will
|
||||
// bring the provenance.
|
||||
owed, err := s.BooksOwedReadModel(ctx, 10)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(owed) != 1 || owed[0].ID != b.ID {
|
||||
t.Fatalf("the parsed book does not owe a reading surface: %+v", owed)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue