Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
fef5604
Give a pad's row the two dates a sweep of files gone for good needs
Jaggob Sep 26, 2026
c98d4d0
Mark the rows of pads leaving Files
Jaggob Sep 26, 2026
f6d079f
Delete the pads of files gone for good in a sweep
Jaggob Sep 26, 2026
00eec47
Let an admin see and release the brake of the sweep of files gone for…
Jaggob Sep 26, 2026
7c69a39
Delete a pad only when its file was seen leaving Files
Jaggob Sep 26, 2026
67c4acb
Run the migrations a branch adds when the app is synced into the stack
Jaggob Sep 26, 2026
b057f3d
Delete the pads of files deleted past the trash in the same request
Jaggob Sep 26, 2026
c183dab
Describe deleting pads as the trash keeps them
Jaggob Sep 26, 2026
6204800
Describe deleting pads as the app does it now
Jaggob Sep 26, 2026
e1203d7
Keep the mark of a file in a team folder's own trash
Jaggob Sep 26, 2026
3679115
Cover pads of files deleted for good end to end
Jaggob Sep 26, 2026
630e933
Install groupfolders in the e2e stack on every Nextcloud major
Jaggob Sep 26, 2026
de1f5d8
Keep the marks of leaving files right through deletes and restores
Jaggob Sep 26, 2026
6fc9dcf
Bring the e2e stack up without groupfolders on an unpinned major
Jaggob Sep 26, 2026
6d13155
List the pads of vanished files in the consistency check
Jaggob Sep 26, 2026
1668e1a
Clear a restore once, and keep a trash's lookup for its end
Jaggob Sep 26, 2026
a5eb5ab
Walk down a folder's tree to find its pads
Jaggob Sep 26, 2026
f7d516e
Offer a new pad from its file when Etherpad has lost the pad
Jaggob Sep 26, 2026
e96ae71
Make a restored file's lost pad anew without asking
Jaggob Sep 26, 2026
36ee346
Leave a restored copy to its open, and ask Etherpad once per restore
Jaggob Sep 27, 2026
74fb485
Mark the pads of deleted files by the file cache's own removals
Jaggob Sep 27, 2026
65b4c6b
Mark a deleted account's files once it is gone, and let a mark wait
Jaggob Sep 27, 2026
a4ab706
Keep a file's pad through the trash, until the file is deleted for good
Jaggob Sep 27, 2026
13c6bc8
Take the sessions of the protected pads a delete takes along
Jaggob Sep 27, 2026
1027481
Rename delete_on_trash to delete_on_permanent_delete
Jaggob Sep 27, 2026
ec810b2
Cover a pad Etherpad lost while its file was in the trash
Jaggob Sep 27, 2026
8ee2eaa
Take over only the rows 1.1.0-beta.1 can have
Jaggob Sep 27, 2026
088da59
Rename delete_on_permanent_delete to delete_pad_with_file
Jaggob Sep 27, 2026
6c8fd3d
Count vanished files by their state, not a column that is gone
Jaggob Sep 27, 2026
93ec474
Count a delete's removals under its node only, and mark no file still…
Jaggob Sep 27, 2026
1dd983a
Refuse a recovery to readers, and keep a lost pad's row when its repl…
Jaggob Sep 28, 2026
f538296
Record the revisions a template's pad starts with, as a restore does
Jaggob Sep 28, 2026
03e1534
Open a pad Etherpad cannot vouch for, and keep a row that no pad can …
Jaggob Sep 28, 2026
f8a0b77
Tell a pad made anew by its text, not by its authors
Jaggob Sep 28, 2026
bbb5e3f
Count nothing a move to the trash or a restore removes, and keep only…
Jaggob Sep 28, 2026
82959be
Take a delete's sessions once it is done, and walk no folder without …
Jaggob Sep 28, 2026
86400a2
Declare the sweep's job in info.xml instead of adding it on every req…
Jaggob Sep 28, 2026
b8e015b
Let an admin's settle take the pads Etherpad refused, and share the m…
Jaggob Sep 28, 2026
42c2345
Warn when the lost-pad check fails on its own, and parse the saved te…
Jaggob Sep 28, 2026
d7e2caf
Describe moves to the trash, sessions taken after a delete, and an em…
Jaggob Sep 28, 2026
4800135
Run the restore test a comment had swallowed
Jaggob Sep 28, 2026
ef16b4e
Count a template's text at revision 0 as saved content
Jaggob Sep 28, 2026
a9f3eea
Leave no row when a failed restore write leaves the file unreadable
Jaggob Sep 28, 2026
7e9013c
Revoke each group's sessions before asking the next
Jaggob Sep 28, 2026
b8faf4d
Cover a template's pad lost from a file 1.1.0-beta.1 wrote at revision 0
Jaggob Sep 28, 2026
ac08c4b
Fold the listener's delete and restore windows into two helpers
Jaggob Sep 28, 2026
460433c
Drop tests that repeat others, and assertions on a field that never s…
Jaggob Sep 28, 2026
63c0c1c
Bring comments and docs up to a trash that keeps the pad
Jaggob Sep 28, 2026
3377286
Remove the test faults and the pass-through LifecycleService
Jaggob Sep 28, 2026
b6e6296
Make a lost pad's replacement one method, and let the recovery read i…
Jaggob Sep 28, 2026
ec20911
Report only vanished files from the consistency check
Jaggob Sep 28, 2026
6e85f2d
Describe a failed replacement once, under the restore
Jaggob Sep 28, 2026
b8974d2
Tell a move to the trash by the file cache, and write marks where a t…
Jaggob Sep 29, 2026
24a3218
Let one pad's failure not stop the sweep, nor a settle ask for it twice
Jaggob Sep 29, 2026
07bb23f
Take a delete's newest sessions first, and ask a group what it holds …
Jaggob Sep 29, 2026
818885d
Bound a delete's walk down a folder, and index the question before it
Jaggob Sep 29, 2026
6717936
Ask a pad's row again right before a sync writes its file
Jaggob Sep 29, 2026
11f9ba5
Drop the test_fault key a debug instance of 1.1.0-beta.1 may have set
Jaggob Sep 29, 2026
61b0a51
Drop what the old trash left in comments, and the lost-pad check's un…
Jaggob Sep 29, 2026
ed73fd2
Run the sweep on every cron tick, and fix two scripts the removed end…
Jaggob Sep 29, 2026
34015e6
Describe this round's delete, sweep and session rules, and fix what t…
Jaggob Sep 29, 2026
381d29f
Replace the index on state, in the one migration from 1.1.0-beta.1
Jaggob Sep 29, 2026
45bf9d3
Keep the new pad when a failed restore write leaves the file unreadable
Jaggob Sep 29, 2026
6c27027
Ask a pad's row again before each write of a sync, after a lock wait too
Jaggob Sep 29, 2026
79f0cdb
Take only an empty group after a pad Etherpad said is gone
Jaggob Sep 29, 2026
187b102
Tell a trash, versions and app data by their storage, not their path …
Jaggob Sep 29, 2026
d307db8
Bound the queries of a delete's walk, not only what it keeps
Jaggob Sep 29, 2026
746f8c6
Describe the kept pad, the sync's checks, the empty group and the sto…
Jaggob Sep 29, 2026
1eebbc9
Say what the list of vanished pads does, and name the moment a sync c…
Jaggob Sep 29, 2026
a48ab7e
Leave out what files:scan drops, and find a deleted user's home from …
Jaggob Sep 29, 2026
56ea30d
Accept a row another request took back, rather than refuse the file
Jaggob Sep 29, 2026
9a81d81
Date a refused pad past its mark, so it counts as tried
Jaggob Sep 29, 2026
ddd2d5c
Treat a restored file as a copy only while its original is there
Jaggob Sep 29, 2026
98c33ad
Keep an admin's opt-out until the setting is taken over
Jaggob Sep 29, 2026
0867a76
Say so when the pending check runs with deleting off
Jaggob Sep 29, 2026
6e4415f
Take the sessions of a legacy group whose pads all leave together
Jaggob Sep 29, 2026
8b1d96e
Describe scan drops, the copy rule, the setting until its takeover, a…
Jaggob Sep 29, 2026
d3734f3
Keep a forced sync from writing a pad Etherpad made anew over its file
Jaggob Sep 29, 2026
d20b0b0
Take the Etherpad sessions of an account being deleted
Jaggob Sep 29, 2026
87cdcd4
Take over the waiting rows an earlier version left in a way this one …
Jaggob Sep 29, 2026
afd3597
Show the old opt-out in the admin form until the setting is taken over
Jaggob Sep 29, 2026
2b6f661
Say why an insert into a trash closes every open delete window
Jaggob Sep 29, 2026
5a9a39f
Name an account's deletion wherever the docs say what ends a session
Jaggob Sep 29, 2026
2e9e09f
End a scan's drop with its own entry
Jaggob Sep 29, 2026
33145b7
Drop test cases that repeat what other tests pin
Jaggob Sep 29, 2026
b6dfbc4
Let a new pad go when its file was written while it was seeded
Jaggob Sep 29, 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
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ This plugin lets you surface pads from an Etherpad instance inside Nextcloud and
- Public folder/file share support for `.pad`
- Periodic sync from Etherpad into `.pad` snapshots
- Pad text is searchable through Nextcloud's full-text search, where that is set up
- Trash deletes on Etherpad (with deferred retry when Etherpad is temporarily unavailable)
- Restore recreates pads from `.pad` snapshot data
- A pad lives as long as its file: the trash keeps it, and it goes once the file is deleted for good
- A `.pad` file whose pad is gone gets a new one from its snapshot: at once when it comes back from the trash, and offered when it is opened

## Screenshots

Expand Down Expand Up @@ -100,7 +100,7 @@ and configure:
- Etherpad API key (OAuth is not required; Etherpad API key auth is used)
- Copy content to `.pad` file interval
- Which pad types users may create (see below)
- Delete-on-trash policy
- Whether a pad is deleted with its file deleted for good
- External public pad policy

Two settings control which pad types the app offers, both enabled by default:
Expand Down Expand Up @@ -235,9 +235,10 @@ existing name asks for confirmation because the previous file is gone for good.

### Trash/Restore

- When a `.pad` file is moved to the Nextcloud trash, the linked Etherpad pad is deleted.
- If Etherpad is temporarily unavailable, delete is deferred and retried.
- When the `.pad` file is restored from the Nextcloud trash, a new pad is recreated and the snapshot from the `.pad` file is replayed.
- A `.pad` file moved to the Nextcloud trash keeps its pad; restored, it has the same pad back, with its history.
- Once the file is deleted for good - from the trash, past it, or with the account that owned it - a background job deletes the pad within minutes, and tries again while Etherpad is unavailable.
- A file restored without a pad - one Etherpad lost, or one trashed under 1.1.0-beta.1 - gets a new pad from the snapshot in the file.
- What each way of deleting a file does to its pad: [docs/deleting-pads.md](docs/deleting-pads.md).

## Troubleshooting

Expand Down Expand Up @@ -293,6 +294,7 @@ existing name asks for confirmation because the previous file is gone for good.
## Documentation

- Architecture: [docs/architecture.md](docs/architecture.md)
- When a pad is deleted: [docs/deleting-pads.md](docs/deleting-pads.md)
- API routes: [docs/api-reference.md](docs/api-reference.md)
- Etherpad integration details: [docs/etherpad-integration.md](docs/etherpad-integration.md)
- `.pad` format: [docs/pad-format.md](docs/pad-format.md)
Expand Down
3 changes: 3 additions & 0 deletions appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
<php min-version="8.1" max-version="8.5"/>
<nextcloud min-version="31.0.9" max-version="34"/>
</dependencies>
<background-jobs>
<job>OCA\EtherpadNextcloud\BackgroundJob\GoneFileSweepJob</job>
</background-jobs>
<repair-steps>
<post-migration>
<step>OCA\EtherpadNextcloud\Migration\RegisterMimeType</step>
Expand Down
3 changes: 0 additions & 3 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,12 @@
['name' => 'padSession#initializeById', 'url' => '/api/v1/pads/initialize-by-id/{fileId}', 'verb' => 'POST'],
['name' => 'padLifecycle#syncStatusById', 'url' => '/api/v1/pads/sync-status/{fileId}', 'verb' => 'GET'],
['name' => 'padLifecycle#syncById', 'url' => '/api/v1/pads/sync/{fileId}', 'verb' => 'POST'],
['name' => 'padLifecycle#trash', 'url' => '/api/v1/pads/trash', 'verb' => 'POST'],
['name' => 'padLifecycle#restore', 'url' => '/api/v1/pads/restore', 'verb' => 'POST'],
['name' => 'padLifecycle#recoverByFileId', 'url' => '/api/v1/pads/recover-from-snapshot/{fileId}', 'verb' => 'POST'],
['name' => 'padLifecycle#findOriginalByFileId', 'url' => '/api/v1/pads/find-original/{fileId}', 'verb' => 'GET'],
['name' => 'admin#saveSettings', 'url' => '/api/v1/admin/settings', 'verb' => 'POST'],
['name' => 'admin#healthCheck', 'url' => '/api/v1/admin/health', 'verb' => 'POST'],
['name' => 'admin#consistencyCheck', 'url' => '/api/v1/admin/consistency-check', 'verb' => 'POST'],
['name' => 'admin#settlePending', 'url' => '/api/v1/admin/settle-pending', 'verb' => 'POST'],
['name' => 'admin#setTestFault', 'url' => '/api/v1/admin/test-fault', 'verb' => 'POST'],
['name' => 'admin#listPadTemplates', 'url' => '/api/v1/admin/templates', 'verb' => 'GET'],
['name' => 'admin#uploadPadTemplate', 'url' => '/api/v1/admin/templates', 'verb' => 'POST'],
['name' => 'admin#deletePadTemplate', 'url' => '/api/v1/admin/templates/delete', 'verb' => 'POST'],
Expand Down
114 changes: 34 additions & 80 deletions docs/api-reference.md

Large diffs are not rendered by default.

145 changes: 74 additions & 71 deletions docs/architecture.md

Large diffs are not rendered by default.

121 changes: 121 additions & 0 deletions docs/deleting-pads.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# When a Pad Is Deleted

SPDX-License-Identifier: AGPL-3.0-or-later

A pad lives in Etherpad; its `.pad` file lives in Nextcloud. The pad lives
as long as the file: the trash keeps it, and it is deleted once the file is
deleted for good. The technical side is in [architecture.md](architecture.md),
under "Trash/Restore" and "Files gone for good".

What counts is the file, not its name: a `.pad` file renamed to something
else keeps its pad, and the pad goes once the file is deleted for good.

This applies to pads this Nextcloud manages, with the admin setting
`delete_pad_with_file` on - the default; `delete_on_trash` before,
whose value it takes over. A `.pad` file that links a pad on another
Etherpad server never deletes that pad.

## Moved to the trash

A file goes to the trash when it is deleted through Files, a desktop or
mobile client, or a WebDAV `DELETE`, in a user's own folders as in a team
folder. So does a file replaced by moving another file onto it (WebDAV
`MOVE` with `Overwrite: T`), and every file in a folder moved to the trash.

The pad stays as it is, and nothing is written into the file.

- **A protected pad** loses its sessions: whoever still has it open can
write no more from their next change on, and nobody opens it until the
file is back. On a folder with many protected pads, what does not fit in
a few seconds is left to expire, within six hours.
- **A public pad** stays reachable by its link, and can be written into.
The file in the trash is not synced, so what is written there is in the
pad, not in the file's snapshot; a restore brings it back with the pad.
- **A `.pad` file that names another user's group** - a legacy Ownpad file
can - takes no sessions along: only a group that holds its pad alone
loses them.

## Restored from the trash

- **A `.pad` file, or a folder with `.pad` files in it:** the file has its
pad back as it was, with its history and anything written into it
meanwhile. Opening it makes a new session for a protected pad.
- **A `.pad` file whose pad Etherpad has lost meanwhile:** a new pad is made
from the snapshot in the file, at once. It holds the content, not the
pad's history. If Etherpad does not answer during the restore, the file
is restored all the same, and its next open offers the new pad. A folder
restored does not ask for each of its files: those whose pad is lost offer
a new one when they are opened.
- **A `.pad` file a share recipient deleted:** Nextcloud puts the file
itself into its owner's trash and a copy into the recipient's. The
owner's restore brings the pad back. The recipient's copy, restored, is a
copy: it holds the file's content as of its last sync - what was written
into the pad after that lives only in the pad, with the original - and
its open offers a new pad from it, while the pad stays with the file in
the owner's trash until that goes.
- **A `.pad` file trashed under an earlier version,** whose trash deleted
the pad (1.1.0-beta.1 and before): a new pad is made from the snapshot in
the file, at once. A
copy of another `.pad` file that was never opened is left as it is: its
pad was not deleted, and opening it offers that pad or a new one.

## Deleted for good

| How the file is deleted for good | When the pad is deleted |
|---|---|
| From the trash: the trash emptied, the item deleted there, expired, or `occ trashbin:cleanup`. A user's trash or a team folder's. | By a background job, within minutes; for a folder's files too. |
| Past the trash: a WebDAV `DELETE` with `X-NC-Skip-Trashbin: true`, the trash app switched off for the user, or a move to the trash that fails or that another app keeps from the trash. | By a background job, within minutes; for a folder's files too. |
| With the account: the account deleted. | The pads of the account's own files - those in its home and its trash, shared ones included - by a background job, within minutes. Files in team folders, and files the account put into folders others shared with it, are not the account's: they stay, and so do their pads. The account's own Etherpad sessions go as the delete starts, as on a logout. |

"A background job" runs on every tick of a five-minute system cron; with
AJAX or webcron, only as often as those run. It deletes the pad of a file
deleted for good once five minutes have passed, so the pad goes within
about ten minutes; the admin page's "Check pending pads" does it at once. When Etherpad does not answer, the job deletes the pad once
it does; until then the admin page counts it as a pending delete.

On Nextcloud 34, Nextcloud reports the files inside a folder deleted for
good under the wrong ids - and a trash emptied as a whole, from the Files
app or with `occ trashbin:cleanup`, is deleted as one folder. The fix
(nextcloud/server#63998) is in 35.0.1; its backport to 34
(nextcloud/server#64497) is planned for 34.0.5 and not merged yet. The app
cannot tell which files they were, so the pads of the `.pad` files inside
such a folder, or in a trash emptied as a whole, stay, and the consistency
check lists them. A `.pad` file deleted on its own - past the trash, or
from the trash by itself or when it expires - and an account deleted, are
not affected.

## Gone without being deleted in Nextcloud

A file can also leave without anyone deleting it in Nextcloud. Its pad
stays:

- files removed outside Nextcloud - on an external storage, or in the data
directory - that a scan then drops;
- a team folder deleted as a whole by an admin, and an external storage
removed. Files that were in the team folder's trash then may go as from
a trash, and take their pads along: on a team folder kept on Nextcloud's
own storage, not on one with a storage of its own.

A pad that stays keeps its content in Etherpad, and nobody reaches it
through Nextcloud any more; a public pad stays reachable by its link. The
consistency check on the admin page counts these pads and lists up to 25 of
them by pad id (`vanished_file_count`). The app keeps their rows: deleting
such a pad in Etherpad does not take it off the list.

## When Etherpad has lost a pad

If Etherpad no longer has a pad - deleted there by hand, or lost with its
database - opening its file says so. Whoever may edit the file can make a
new pad from the content saved in it; from then on the file opens the new
pad. The same happens for a public pad that someone visited by its link
after it was lost, which Etherpad makes anew, empty; an editor still open on
the file does not write that empty pad into it as it closes, so the content
stays for the new pad. A copied `.pad` file,
which has no pad of its own, offers the same. A file restored from the
trash does not ask: it gets its new pad on its own.

## With `delete_pad_with_file` off

The app deletes no pad at all: the pads of files deleted for good stay, and
the admin page counts them as pending deletes. Switched back on, the job
deletes them.
49 changes: 31 additions & 18 deletions docs/etherpad-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,20 +54,18 @@ that file destroy their group, their pad and their sessions. A group that
holds only the pad being deleted, or nothing at all, has nothing else to
lose.

It answers true when it removed something, the pad or the empty group a
protected pad left behind, and false when Etherpad says there was nothing
left: no such pad, or no such group. A caller that has just heard the pad
is gone says so (`knownAbsent`); a public pad then costs no call, a
protected one is still asked about its group.
It is done when it removed something, the pad or the empty group a
protected pad left behind, and when Etherpad says there was nothing left:
no such pad, or no such group. A caller that has just heard the pad is
gone says so (`knownAbsent`); a public pad then costs no call, a protected
one is still asked about its group.

A group Etherpad cannot list is given up by default: the pad goes alone,
the half that was always safe, and the empty group stays. A caller that
keeps its row and tries again says so (`retried`) and gets the failed read
instead, with nothing removed: the trash, which then owes the deletion,
and the sweep for a file gone for good while the deletion has been owed
for less than a day, counted from the trash. One that cannot try again -
the sweep once it has taken a trashed file's row, the clean-up after a
replacement or a released row - takes the default.
instead, with nothing removed: the sweep of files gone for good, which
tries again an hour later. One that cannot try again - the clean-up after
a replacement - takes the default.

The shape rule itself lives in `Util\PadId` and is the same one that
classifies a binding as protected. It used to be stricter here, so a pad
Expand Down Expand Up @@ -159,9 +157,10 @@ which is how the open request can read it in the first place.
open of any other protected pad happened to overwrite the cookie and cut
it off; that only ever helped if the user opened another pad, and did
nothing otherwise. Revoking a share does not end the Etherpad sessions
already issued for it: only expiry, a logout, or removing the group
behind the pad clears them, so the window is the session TTL either way
— it is just no longer shortened by accident.
already issued for it: only expiry, a logout, the recipient's account
being deleted, or removing the group behind the pad clears them, so the
window is the session TTL either way — it is just no longer shortened
by accident.

### Author Resolution Strategy

Expand Down Expand Up @@ -227,8 +226,9 @@ away itself:
every socket message and keeps the session id it was handed when the pad
connected – read in 2.7.3, 3.0.0 and 3.3.3 – so a session that expires
mid-edit rejects the next keystroke, and no later cookie reaches that
socket. Revocation fires on an explicit logout and nowhere else, and is
capped, so for most sessions the lifetime is what bounds the window.
socket. Revocation fires on an explicit logout, on the account's
deletion and on a delete of the pad's file (below), and is capped, so
for most sessions the lifetime is what bounds the window.
- Expired sessions are left to the background sweep described below. Only
what is expired by both clocks counts as expired: Etherpad judges
`validUntil` with its own, so a session ours calls dead may still be
Expand All @@ -246,9 +246,22 @@ cached id is therefore not dropped when an open fails – an emptied cache
cannot be told from a user who never opened a protected pad, and a logout
after a brief outage would revoke nothing.

**Only logout.** Losing a share does not revoke anything, and neither does
a permission downgrade, a deleted or disabled account, or a deleted public
link. A session issued before any of those stays valid until `validUntil`.
**A delete of the file revokes the pad's sessions** – every session of
the pad's group, whoever it was issued to, once the file goes to the
trash or past it, for a folder's protected pads too: the trash keeps the
pad, and a session would keep giving it to whoever holds one. Which
groups, and within what budget: `docs/architecture.md`, "Trash/Restore".

**Deleting an account revokes its sessions**, as a logout does, as the
delete starts (`RevokeSessionsOnAccountDeleteListener`): Nextcloud removes
the account's settings, the cached author among them, before it reports
the account gone. A delete the user backend then refuses has lost them as
a logout would, and the next open makes new ones.

**Otherwise, only expiry.** Losing a share does not revoke anything, and
neither does a permission downgrade, a disabled account, or a deleted
public link. A session issued before any of those stays valid until
`validUntil`.
Covering them one event at a time means enumerating every way access can
end, and that list has no natural end – a public link in particular opens
under its own Etherpad author whose id is deliberately never cached, so
Expand Down
8 changes: 5 additions & 3 deletions docs/pad-format.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,9 @@ Protected + external is not supported.
- `trashed` / `purged`
- legacy parser compatibility only; new writes do not use these states

The DB binding table uses `active`, `pending_delete` and `restore_pending`. Successful trash deletes
the binding row; restore can recreate it from the `.pad` frontmatter and snapshot.
The DB binding table uses `active`, and `pending_delete` for a file deleted for good whose pad
has yet to go. A trash keeps the binding row and the pad; a file restored without a row, or
whose pad Etherpad lost, gets a new pad from the `.pad` frontmatter and snapshot.
External pads are not managed in the DB binding table, so trash/restore only moves
the Nextcloud file and never creates, deletes, or restores anything on the remote
Etherpad server.
Expand All @@ -129,7 +130,7 @@ Snapshot write flow:

- `PadFileService::withExportSnapshot(...)` builds the new `.pad` content after an Etherpad export.
- `PadFileLockRetryService::putContentWithSyncLockRetry(...)` writes that content back to the Nextcloud file with bounded lock retry.
- Stored snapshots are read by `RestoreService` when restoring a pad and by the forced sync when comparing content. No viewer path reads them.
- Stored snapshots are read by `RestoreService` when restoring a pad, by the forced sync when comparing content, and by an open that may write and the sync to tell whether Etherpad has lost the pad. No viewer path shows them.
- External public pad create/sync paths both use the validated, host-pinned `/export/txt` fetch internally (via `ExternalPadExportFetcher`) and store no HTML snapshot:
- create uses `ExternalPadExportFetcher::normalizeAndFetchExternalPublicPadTextOrEmpty(...)`, allowing the `.pad` file to be created with an empty initial snapshot if the export is not available yet.
- sync uses `ExternalPadExportFetcher::normalizeAndFetchExternalPublicPadText(...)`, keeping later export failures visible.
Expand All @@ -138,6 +139,7 @@ Snapshot write flow:

- Sync writes only when the upstream snapshot actually differs.
- `force=1` requests an immediate upstream re-check, but unchanged snapshots are still not rewritten.
- A pad Etherpad made anew in place of the file's - without a single revision, with other text than the file saved - is not written, forced or not: the sync answers `pad_missing`, and the file keeps its content for a new pad.
- On successful sync:
- `snapshot_rev` is updated
- body is replaced:
Expand Down
11 changes: 0 additions & 11 deletions docs/release-process.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ What it does:
- If Nextcloud test credentials are present, runs core E2E checks:
- pad flow
- protected cookie contract (session cookie attrs + no `HttpOnly` for current Etherpad runtime compatibility)
- lifecycle state guards
- public folder share flow
- public single-file share flow
- external URL security checks
Expand Down Expand Up @@ -92,23 +91,13 @@ RUN_FAILURE_PATHS=1 FAILURE_PATHS_PREPARED=1 NC_BASE_URL=... NC_USER=... NC_APP_
Included:

- sync failure path
- trash deferred-delete failure path
- restore failure path

Optional debug-only lifecycle fault-injection checks:

```bash
RUN_DEBUG_FAULT_PATHS=1 NC_BASE_URL=... NC_USER=... NC_APP_PASSWORD=... ./tests/integration/release-check.sh "/release-debug-faults"
```

Notes:

- `release-check.sh` will only run failure-path tests when both flags are set:
- `RUN_FAILURE_PATHS=1`
- `FAILURE_PATHS_PREPARED=1`
- This prevents false failures on healthy environments where outage conditions were not prepared.
- Requires admin credentials.
- Requires Nextcloud `debug` mode enabled.

## 3) Tagging

Expand Down
Loading
Loading