WritePolicy.h header

#include <ew/app/WritePolicy.h>

Namespace ew::app

Enumerations

enum class WriteRefusal { None, Unresolved, ReadOnlyLibrary, Locked }

Why a write was refused, or that it was allowed.

A reason rather than a bare bool for the same argument ew::core::policy::Decision makes: the caller has to say something TRUE to the user, and these refusals are not interchangeable. Telling someone their locked chapter "is in a read-only library" sends them looking for a mount they do not have; telling them a library object "is locked" sends them to an Unlock command that would itself be refused.

Kept separate from ew::core::policy::Decision on purpose. Where an object is MOUNTED is not a property of the object, and libs/core has no vocabulary for it – see mayWrite() for why teaching it one would be a silent permission escalation.

Functions

bool ew::app::policy::mayWrite(const ew::app::library::MountedLibraries &libraries, const ew::core::project::Project &mainProject, ew::core::foundation::ContentId id)

Whether id may be written at all – writeRefusal() reduced to a bool, for the call sites that only enable a control and never explain themselves.

Why this composes at the app layer instead of becoming a fourth rule inside the core gate. The tempting shape is an ObjectPolicy::inReadOnlyLibrary field and a DenyReadOnlyLibrary decision. It is the wrong one: every existing caller builds an ObjectPolicy by hand, so a new field defaults to false and a caller who forgets to populate it gets Allow – a silent permission ESCALATION, and strictly worse than the scattered checks it replaced. A missing argument here is a compile error instead.

WriteRefusal ew::app::policy::writeRefusal(const ew::app::library::MountedLibraries &libraries, const ew::core::project::Project &mainProject, ew::core::foundation::ContentId id)

Why id may not be written, considering both axes: the object's lock and the mount it lives in.

ASK THIS, NOT ONE HALF OF IT. Before this existed the desktop asked the library question in nine places and the lock question in six, and no place asked both – so the manuscript tree let a LOCKED document's tags, audiences and AI-exclusion be changed while the MCP tools refused the very same edits, because those resolve with forWrite and pass the lock to the gate. One function that answers the whole question is what stops the next write path from picking up one axis and missing the other.

Refusals are reported most-fundamental first: a read-only library outranks a lock, because in a library the writer cannot act on "it is locked" – unlocking is itself a write into a repo that takes none.

The lock half goes through ew::core::policy::authorize (via isWritable()); the mount half is ew::app::library::MountedLibraries::isWritable. Unresolved is a refusal, not an error: an id naming nothing is not writable, and failing closed here means a caller that forgets to null-check is merely useless rather than dangerous.