| 138 | } |
| 139 | |
| 140 | Namespace::CommitOutcome Namespace::commit_one(fid_t field_id) { |
| 141 | const Field* f = find_field(field_id); |
| 142 | if (!f) return CommitOutcome::Unknown; |
| 143 | auto it = _draft.find(field_id); |
| 144 | if (it == _draft.end()) return CommitOutcome::NoChange; |
| 145 | Value v = it->second; |
| 146 | if (f->has_flag(FF_READ_ONLY)) { |
| 147 | _draft.erase(it); |
| 148 | return CommitOutcome::ReadOnly; |
| 149 | } |
| 150 | if (!f->validate(v)) { |
| 151 | return CommitOutcome::InvalidValue; |
| 152 | } |
| 153 | _draft.erase(it); |
| 154 | |
| 155 | // Write-only command field: fire the setter as a one-shot action. |
| 156 | // Do not promote to working, do not mark dirty, do not persist. |
| 157 | // Subsequent reads return None and the action does not replay on |
| 158 | // reboot (working map stays at the seeded default). |
| 159 | if (f->has_flag(FF_WRITE_ONLY)) { |
| 160 | if (f->setter && !f->setter(v)) return CommitOutcome::SetterFailed; |
| 161 | return CommitOutcome::AppliedLive; |
| 162 | } |
| 163 | |
| 164 | // Stateful field: promote into working and persist-dirty regardless |
| 165 | // of live/reboot flag — the file on disk is the source of truth |
| 166 | // for next boot. |
| 167 | _working[field_id] = v; |
| 168 | _dirty_for_persist = true; |
| 169 | |
| 170 | if (f->has_flag(FF_LIVE_APPLY) && f->setter) { |
| 171 | if (!f->setter(v)) return CommitOutcome::SetterFailed; |
| 172 | return CommitOutcome::AppliedLive; |
| 173 | } |
| 174 | if (f->has_flag(FF_REBOOT_REQUIRED)) { |
| 175 | return CommitOutcome::AppliedReboot; |
| 176 | } |
| 177 | // Field has neither LIVE nor REBOOT — value is stored but no runtime |
| 178 | // effect declared. Treat as applied-live (no-op). |
| 179 | return CommitOutcome::AppliedLive; |
| 180 | } |
| 181 | |
| 182 | } } |