Found by the domain:identity execution seat while measuring whether #7675 ("4 of 10 declared sys_audit_log actions have no writer anywhere") can close. Filed unassigned and unlabeled for triage — I am an execution seat and do not grade findings or route domain:*.
Same defect class as #7675, on a value #7675's own survey did not name. Measured on origin/main, not inferred.
What
packages/plugins/plugin-audit/src/objects/sys-audit-log.object.ts:125 declares:
action: Field.select(
['create', 'update', 'delete', 'restore', 'login', 'logout', 'config_change', 'import'],
Nothing writes restore. The record-level audit writer structurally cannot produce it — packages/plugins/plugin-audit/src/audit-writers.ts:245:
function actionFor(event: string): 'create' | 'update' | 'delete' | null {
restore is not in the return type, and audit-writers.ts:1105-1106 early-returns on null. A tree-wide grep for any form of action: 'restore' across packages/**/*.ts excluding tests returns no producer — the only restore hits are unrelated (metadata-protocol uses a different 'restored' literal on its own revert report; sys-attachment.object.ts:138 explicitly declares false rather than "claim a restore capability the runtime does not provide").
⚠️ Two declarations assert the opposite, which is what makes this more than tidiness
1. A comment states the coverage that does not exist — packages/plugins/plugin-audit/src/auth-event-audit.ts:11:
create/update/delete/restore and nothing else — four of the declared…
It names restore as one of the four covered actions. actionFor()'s own signature contradicts it. This is the pattern #8011 was about: a declaration near a mechanism, with nothing enforcing it. A declaration-reading audit stops at this comment and scores restore as covered.
2. A shipped list view offers it as a filter value — sys-audit-log.object.ts:55:
filter: [{ field: 'action', operator: 'in', value: ['create', 'update', 'delete', 'restore'] }],
The data_changes view advertises a value that can never match. Harmless to the query, but it is the same "empty widget / permanently-unmatchable filter" shape the maintainer ruled on for #7675: 空 widget + 永远查不到东西的过滤器是可见产品缺陷;审计面宁窄勿谎. Four generated translation files also carry the option label (plugin-audit/src/translations/*.objects.generated.ts:27).
Status of #7675's named values, for context
Every value #7675 actually named is now resolved, which is why this one surfaced:
| action |
state |
login |
✅ writer at auth-session-audit.ts:138 (#8144 → PR #8244) |
logout |
✅ writer at auth-session-audit.ts:162 (#8144 → PR #8244) |
config_change |
✅ writer landed (#8145 → PR #8288) |
export |
✅ retired from the enum (#8147 → PR #8200) |
permission_change |
✅ retired from the enum (#8147 → PR #8200) |
import |
✅ real writer at plugin-auth/src/admin-import-users.ts:515 |
restore |
❌ no writer — this card |
Not adjudicated here
⛔ I have deliberately not decided between the two obvious shapes (give restore a writer, or retire it from the enum/view/translations per the 宁窄勿谎 principle). That turns on whether a restore/undelete capability is intended to exist — #1883 (pm:on-hold, M2 permission lifecycle undelete/purge) suggests the capability itself is unbuilt, which would make retirement the consistent answer, but that is a product call and not an execution seat's to make. Recorded plainly per the file-plainly rule.
Whichever way it goes, the false comment at auth-event-audit.ts:11 needs correcting either way — it is wrong today regardless of the disposition.
Related: #7675 (parent survey), #8147 (the retirement that this measurement re-checks), #1883, #8011 (same "unenforced declaration" class), #8203 (a select option enum is unenforceable once the field is readonly — the mechanism that lets this persist undetected).
Found by the
domain:identityexecution seat while measuring whether #7675 ("4 of 10 declaredsys_audit_logactions have no writer anywhere") can close. Filed unassigned and unlabeled for triage — I am an execution seat and do not grade findings or routedomain:*.Same defect class as #7675, on a value #7675's own survey did not name. Measured on
origin/main, not inferred.What
packages/plugins/plugin-audit/src/objects/sys-audit-log.object.ts:125declares:Nothing writes
restore. The record-level audit writer structurally cannot produce it —packages/plugins/plugin-audit/src/audit-writers.ts:245:restoreis not in the return type, andaudit-writers.ts:1105-1106early-returns onnull. A tree-wide grep for any form ofaction: 'restore'acrosspackages/**/*.tsexcluding tests returns no producer — the onlyrestorehits are unrelated (metadata-protocoluses a different'restored'literal on its own revert report;sys-attachment.object.ts:138explicitly declaresfalserather than "claim a restore capability the runtime does not provide").1. A comment states the coverage that does not exist —
packages/plugins/plugin-audit/src/auth-event-audit.ts:11:It names
restoreas one of the four covered actions.actionFor()'s own signature contradicts it. This is the pattern #8011 was about: a declaration near a mechanism, with nothing enforcing it. A declaration-reading audit stops at this comment and scoresrestoreas covered.2. A shipped list view offers it as a filter value —
sys-audit-log.object.ts:55:The
data_changesview advertises a value that can never match. Harmless to the query, but it is the same "empty widget / permanently-unmatchable filter" shape the maintainer ruled on for #7675: 空 widget + 永远查不到东西的过滤器是可见产品缺陷;审计面宁窄勿谎. Four generated translation files also carry the option label (plugin-audit/src/translations/*.objects.generated.ts:27).Status of #7675's named values, for context
Every value #7675 actually named is now resolved, which is why this one surfaced:
loginauth-session-audit.ts:138(#8144 → PR #8244)logoutauth-session-audit.ts:162(#8144 → PR #8244)config_changeexportpermission_changeimportplugin-auth/src/admin-import-users.ts:515restoreNot adjudicated here
⛔ I have deliberately not decided between the two obvious shapes (give
restorea writer, or retire it from the enum/view/translations per the 宁窄勿谎 principle). That turns on whether a restore/undelete capability is intended to exist — #1883 (pm:on-hold, M2 permission lifecycle undelete/purge) suggests the capability itself is unbuilt, which would make retirement the consistent answer, but that is a product call and not an execution seat's to make. Recorded plainly per the file-plainly rule.Whichever way it goes, the false comment at
auth-event-audit.ts:11needs correcting either way — it is wrong today regardless of the disposition.Related: #7675 (parent survey), #8147 (the retirement that this measurement re-checks), #1883, #8011 (same "unenforced declaration" class), #8203 (a
selectoption enum is unenforceable once the field isreadonly— the mechanism that lets this persist undetected).