Prototype: RecipeDraft retains copies of name/ingredient/step data; snapshot returns Draft with fresh nested lists. Empty content and duplicate items are valid; no name validation.
Final: addUser requires nonempty ID and only read/write permissions; validate before duplicate check, copy permissions. add requires nonempty current unique name and write permission; returns recipe1,recipe2,... only on success. update appends even identical content and returns Integer version; missing/no permission/name conflict returns null. get requires read; null version selects latest, invalid version returns null. history returns detached revisions in order or empty for denied/missing. rollback requires write and appends selected content if its historical name is available; preserve all versions. byIngredient requires read, matches current exact membership once per recipe, sorts ingredient-list size/name/ID. Empty lists/items and duplicates preserve order; empty names raise before permission checks.
The complete public API is in Exercise.java. All domain classes are public static nested classes of Exercise; construct them as new Exercise.ClassName(...). Do not implement a solution in the test runner. Each instance owns independent state. Unless explicitly allowed, null inputs are outside the domain. Failed operations leave state unchanged; validation happens before mutation.
Complete the starter code, then run the checks.
Results and errors will appear here.
Java runs on this device with CheerpJ 4.3.