Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
afacae6
docs(soft-delete): spec fazy 05b — nagrobki dla konsumentow przyrosto…
mpasternak Aug 15, 2026
0688931
docs(soft-delete): plan implementacji fazy 05b (nagrobki) + korekta s…
mpasternak Aug 16, 2026
b4d11fb
feat(cerif): kontrakt przynaleznosc() + roznica nagrobkow w bazie pro…
mpasternak Aug 16, 2026
9ff5121
feat(cerif): przynaleznosc() providerow o atrybucji wlasnej + izolacj…
mpasternak Aug 16, 2026
c28a830
feat(cerif): przynaleznosc() providerow pochodnych (konferencje, gran…
mpasternak Aug 16, 2026
9cea166
feat(cerif): strona() paginuje nadzbior i zwraca pary (obiekt, nagrobek)
mpasternak Aug 16, 2026
b33920a
feat(cerif): ListRecords/ListIdentifiers emituja naglowek status=deleted
mpasternak Aug 16, 2026
0393296
feat(cerif): GetRecord na usunietym zwraca nagrobek zamiast idDoesNot…
mpasternak Aug 16, 2026
f39a1e7
feat(cerif): Identify deklaruje deletedRecord=transient
mpasternak Aug 16, 2026
da1f85f
test(cerif): cztery drogi znikniecia, okno from/until, granica strony
mpasternak Aug 16, 2026
6d0d375
feat(api_v1): endpoint /usuniete/ — nagrobki bez tresci rekordu
mpasternak Aug 16, 2026
2ccee85
test(cerif)+docs: walidacja XSD nagrobkow, newsfragment, ostrzezenie …
mpasternak Aug 16, 2026
a513192
chore(pre-commit): nie normalizuj bialych znakow w vendorowanych XSD
mpasternak Aug 16, 2026
01840dc
feat(api_v1): /usuniete/ stronicuje, z LIMIT/OFFSET po stronie bazy
mpasternak Aug 16, 2026
b9bf685
docs(soft-delete): handoff — /usuniete/ stronicuje; nowy dlug: asymet…
mpasternak Aug 16, 2026
6b10b7a
docs(soft-delete): handoff — stan stosu PR-ow + decyzja o bazie fazy 06
mpasternak Aug 16, 2026
1299820
docs(soft-delete): baza fazy 06 to feat/soft-delete-05b (decyzja wlas…
mpasternak Aug 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,23 @@ repos:
# `mkdocs build --strict` parsuje plik bez problemu.
exclude: ^(docker-compose\.test\.ci\.yml|mkdocs\.yml)$
- id: check-toml
# `src/cerif_export/tests/xsd/` to VENDOROWANE schematy XSD (profil
# OpenAIRE CERIF 1.2 + koperta OAI-PMH 2.0) — kopie plików cudzych,
# pobrane wprost od wydawcy. Nie normalizujemy w nich białych znaków:
# plik ma zostać porównywalny bajt-w-bajt z upstreamem, żeby przy
# podbiciu wersji diff pokazywał ZMIANY SCHEMATU, a nie nasze
# przeformatowanie. Wyjątek nie obejmuje naszego pliku spinającego
# `oai-pmh-z-profilem.xsd` — ten piszemy sami.
- id: end-of-file-fixer
exclude: ^baseline-sql/baseline\.sql$
exclude: &wylaczone_z_bialych_znakow |
(?x)^(
baseline-sql/baseline\.sql$
| src/cerif_export/tests/xsd/(?!oai-pmh-z-profilem\.xsd).*\.xsd$
)
- id: trailing-whitespace
exclude: ^baseline-sql/baseline\.sql$
exclude: *wylaczone_z_bialych_znakow
- id: mixed-line-ending
exclude: ^baseline-sql/baseline\.sql$
exclude: *wylaczone_z_bialych_znakow
- id: check-added-large-files
args: ['--maxkb=500']
# .test_durations to celowy, generowany plik danych pytest-split
Expand Down
12 changes: 12 additions & 0 deletions docs/deweloper/eurocris-co-jeszcze.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,18 @@ zombie w indeksie.
`eksport_cerif_osoby=False` — sam set osób (autorzy zostają wtedy przy
publikacjach jako samo imię i nazwisko, bez `@id`, ORCID-a i afiliacji).

**⚠️ Operacyjnie — od fazy 05b (nagrobki):** przestawienie
`eksport_cerif_osoby` na `False` wystawi w najbliższym harveście
**nagrobki dla WSZYSTKICH autorów uczelni naraz** (nagłówki ze statusem
`deleted`). Zachowanie jest poprawne i zamierzone — harvester ma te osoby
u siebie usunąć, a to jest właśnie skutek, o który chodzi przy decyzji
RODO. Jednorazowo jest to jednak bardzo duży wsad: przy kilku tysiącach
autorów harvest przyrostowy zwróci tyle samo nagrobków, rozłożonych na
wiele stron `resumptionToken`. Operator powinien o tym wiedzieć przed
przestawieniem przełącznika i najlepiej zrobić to w oknie serwisowym,
a nie w środku dnia. To samo dotyczy ponownego włączenia — autorzy wrócą
wtedy jako rekordy żywe, znów wszyscy naraz.

---

## E. Drobiazgi z profilu — pola dziś pomijane
Expand Down
106 changes: 82 additions & 24 deletions docs/superpowers/HANDOFF-soft-delete-faza-06.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,37 @@
| | |
|---|---|
| Stan fazy 05a | gałąź `feat/soft-delete-05`, PR do `feat/soft-delete-04` (stacked) |
| Punkt startowy fazy 06 | `feat/soft-delete-05` |
| Punkt startowy fazy 06 | **`feat/soft-delete-05b`** (decyzja właściciela 2026-08-16) |
| Plan fazy 06 | [`plans/2026-06-04-soft-delete-06-softdeletelog.md`](plans/2026-06-04-soft-delete-06-softdeletelog.md), 914 linii — **istnieje, nie trzeba brainstormingu ani specu** |
| Migracje fazy 05a | `pbn_export_queue/0011` (pole `operacja`), `pbn_api/0080` (`SentData.withdrawn_at`), `zglos_publikacje/0028` (state-only, dług fazy 04) |
| **Zakres rozdzielony** | nagrobki OAI-PMH/CERIF/REST **wyszły do fazy 05b** (decyzja właściciela 2026-08-10) |
| **Zakres rozdzielony** | nagrobki OAI-PMH/CERIF/REST **wyszły do fazy 05b** (decyzja właściciela 2026-08-10), ZROBIONE — patrz §5 |

### Stos PR-ów (stan 2026-08-16, wszystkie OTWARTE, żaden niescalony)

```
#312 feat/soft-delete -> dev
#745 feat/soft-delete-04 -> feat/soft-delete
#755 feat/soft-delete-05 -> feat/soft-delete-04
#767 feat/soft-delete-05b -> feat/soft-delete-05 (faza 05b, nagrobki)
```

**Baza fazy 06: `feat/soft-delete-05b`** — rozstrzygnięte, stos rośnie
liniowo. (Technicznie faza 06 **nie zależy** od 05b: nagrobki liczą się
z dopełnienia ekspozycji, nie z `SoftDeleteLog`, a pliki są rozłączne —
06 rusza `bpp/models/soft_delete.py` i sygnały, 05b ruszała `cerif_export/`
+ `api_v1/`. Odbicie od 05 też by działało; wybrano liniowość.)

```bash
git checkout feat/soft-delete-05b && git checkout -b feat/soft-delete-06
```

⚠️ **PR #767 nie był jeszcze recenzowany ani scalony**, więc faza 06
dziedziczy jego commity. Gdyby review wymusiło zmiany w 05b, trzeba będzie
je przenieść (rebase) do gałęzi 06.

⚠️ Jeśli zadaniem sesji jest **dopilnowanie samego PR #767** (review,
poprawki, merge), a nie start fazy 06 — ten handoff jest złym punktem
startu. Zacznij od `gh pr view 767` i od §5.

⚠️ **Faza 05 była w planie JEDNĄ fazą o dwóch niezależnych podsystemach.**
Wycofanie z PBN miało drobiazgowy plan (1286 linii); nagrobki miały baner
Expand Down Expand Up @@ -132,29 +160,59 @@ per-aplikacja. Szukanie dziedziczących tylko w `bpp/` nie wystarcza.

---

## 5. Faza 05b — nagrobki (przed fazą 07, nie przed 06)

Wyszła z fazy 05 decyzją właściciela 2026-08-10. **Nie ma jeszcze specu ani
planu** — potrzebuje własnego cyklu brainstorming → spec → plan → PR.

Punkt startowy rozpoznany:

- `src/cerif_export/const.py:115` → **`DELETED_RECORD = "no"`**. To nie jest
„brak funkcji", to **obietnica w `Identify`**: harvester ma prawo nie pytać
przyrostowo o usunięcia. Zmiana na `persistent`/`transient` to zmiana
kontraktu, nie dopisanie atrybutu.
- Emisja nagłówka: `oai/czasowniki.py` (`_naglowek()`), `Identify` w `:198`.
- **Architektura providerów jest gotowa**: `ProviderEncji.strona()`
(`providers/base.py`) stronicuje keysetem po
`(COALESCE(ostatnio_zmieniony, EPOKA), pk)`, a soft-delete bumpuje
`ostatnio_zmieniony`. Husk wpadłby więc **naturalnie na właściwe miejsce
w kursorze**. Brakuje wyłącznie poszerzenia `queryset()` o kosz i flagi
„to nagrobek" na obiekcie.
## 5. Faza 05b — nagrobki: ZROBIONA (2026-08-16)

Wyszła z fazy 05 decyzją właściciela 2026-08-10; zaimplementowana na gałęzi
`feat/soft-delete-05b`.

- Spec: [`specs/2026-08-15-soft-delete-nagrobki-design.md`](specs/2026-08-15-soft-delete-nagrobki-design.md)
- Plan: [`plans/2026-08-16-soft-delete-05b-nagrobki.md`](plans/2026-08-16-soft-delete-05b-nagrobki.md)

Co weszło:

- `ProviderEncji.przynaleznosc(uczelnia, model)` — drugi człon kontraktu:
atrybucja tenanta BEZ reguł ekspozycji. `nagrobki()` to różnica
`przynaleznosc − queryset`. Rozszczepienie jest konieczne, bo dopełnienie
całej widoczności wystawiałoby w multi-hosted rekordy cudzych uczelni
(`widoczne_jednostki()` filtruje `uczelnia=` wprost).
- `strona()` paginuje **nadzbiór** i zwraca pary `(obiekt, czy_nagrobek)` —
jeden strumień, jeden kursor keyset. **To zmiana kształtu zwrotki**:
wszyscy wołający (OAI + suity testowe) zostali dostosowani, testy poza
`test_nagrobki.py` używają helpera `tests/pomocnicze.py::strona_zywych`.
- `status="deleted"` w `ListRecords`, `ListIdentifiers` i `GetRecord`
(bez `<metadata>`); `GetRecord` na rekordzie usuniętym zwraca nagrobek,
a `idDoesNotExist` zostaje dla identyfikatorów spoza tenanta.
- `DELETED_RECORD = "transient"` — obietnica w `Identify` jest teraz prawdziwa.
- `/api/v1/usuniete/` — identyfikator + data, nigdy treść. Zakres **węższy**
niż OAI (sam kosz), decyzja D5 specu. Stronicowany
(`BppLimitOffsetPagination`), przy czym `ORDER BY` i `LIMIT/OFFSET`
wykonuje baza: sześć modeli kosza łączy `UNION ALL` o wspólnym kształcie
`(etykieta modelu, pk, deleted_at)`. Sklejanie list w Pythonie byłoby tu
pesymalizacją — każde żądanie ciągnęłoby cały kosz, a stron jest wiele.
- Faza **nie dodaje migracji** (dlatego ostrzeżenie o `eksport_cerif_osoby`
poszło do `docs/deweloper/eurocris-co-jeszcze.md`, a nie w `help_text`).

Co zostaje otwarte dla faz dalszych:

- **Asymetria gate'u na `.update(deleted_at=...)`.** `BppSoftDeleteQuerySet`
blokuje bulk-ustawienie znacznika (omijałoby `post_save`, kaskadę
`*_Autor`, `SoftDeleteLog` i reversion), ale gate dziedziczy tylko
`AutorQuerySet` — `BppDeletedQuerySet` publikacji **nie**. Czyli
`Autor.deleted_objects.filter(...).update(deleted_at=...)` rzuca
`RuntimeError`, a to samo na `Wydawnictwo_Ciagle` przechodzi. Wygląda na
przeoczenie, nie na decyzję. Testy fazy 05b tego nie wykorzystują (opierają
się na zegarze), więc domknięcie gate'u ich nie zepsuje.
- Modele soft-delete to nadal publikacje (faza 02) + `Autor` (faza 04).
Słowniki (`Zrodlo`, `Konferencja`, `Projekt`, `Jednostka`) — **nie**;
ich nagrobki biorą się z dopełnienia ekspozycji, nie z kosza, więc
twarde skasowanie takiego wiersza nadal znika po cichu.
- ⚠️ `z_datestampem()` niesie dwie zapisane blizny (`Trunc` do sekundy,
`tzinfo=UTC`) — obie o duplikatach na granicy strony. Nagrobki muszą iść
tą samą ścieżką.
- Modele soft-delete: publikacje (faza 02) + `Autor` (faza 04). Słowniki
(`Zrodlo`, `Konferencja`, `Projekt`, `Jednostka`) — **nie**.
`tzinfo=UTC`) — obie o duplikatach na granicy strony. Nagrobki idą tą samą
ścieżką; pilnuje tego
`test_harvest_po_tokenach_nie_gubi_i_nie_dubluje_na_granicy_nagrobka`.
- Ograniczenia świadomie poza zakresem (sekcja „Ograniczenia" specu):
przepięcie autorstwa do innej uczelni bez śladu w koszu nadal znika po
cichu; rekordy nigdy-niewidoczne też dostają nagrobek.

---

Expand Down
Loading