maelys-git
Level 2 — Structured deny codes
Keep machine-stable denial codes separate from human-readable messages.Continue from Level 1 — Minimal gate. Keep the declarations and helpers from the earlier levels; this page extends the same wrapper in C and Python.
Level 2 separates machine-stable decision codes from human-readable messages.
The policy derives facts such as deny("push", "GIT_FORCE_PUSH"), while the
wrapper maps GIT_FORCE_PUSH to a user-facing message. This keeps audit records
stable even when wording, localization, or UI messages change.
The principle
deny("push", "GIT_FORCE_PUSH") ← stable code in policy
↓
C lookup table:
"GIT_FORCE_PUSH" → "force-push is forbidden"
"GIT_PROTECTED_BRANCH" → "direct push to protected branch"
"GIT_RELEASE_BRANCH" → "only release managers can push to release branches"
"GIT_BLOCKED_USER" → "user is not allowed to commit"
"GIT_BAD_COMMIT_PREFIX" → "commit message must start with feat: fix: ..."
"GIT_DELETE_PROTECTED" → "deleting a protected branch is forbidden"
"GIT_SENSITIVE_FILE" → "this file requires explicit approval"Human messages can change without touching the policy. The deny code is what appears in audit records and logs — it is stable across policy versions.
Message table in the wrapper
static const struct { const char *code; const char *message; }
deny_messages[] = {
{ "GIT_FORCE_PUSH", "force-push is forbidden" },
{ "GIT_PROTECTED_BRANCH", "direct push to protected branch is forbidden" },
{ "GIT_BLOCKED_USER", "user is not allowed to commit" },
{ "GIT_BAD_COMMIT_PREFIX", "commit message must start with feat: fix: chore: docs: refactor:" },
{ "GIT_DELETE_PROTECTED", "deleting a protected branch is forbidden" },
{ "GIT_SENSITIVE_FILE", "this file requires explicit approval" },
{ "GIT_RELEASE_BRANCH", "only release managers can push to release branches" },
{ NULL, NULL }
};
static const char *message_for_code(const char *code) {
for (int i = 0; deny_messages[i].code; i++)
if (strcmp(deny_messages[i].code, code) == 0)
return deny_messages[i].message;
return code; /* fallback: show code if unknown */
}What you have learned
Keep this model
Separate denial codes from human messages- Policy produces stable codes
A denial fact carries a machine-readable code identifying the reason. That code can remain stable while wording changes.
- The application presents the message
The wrapper maps the code to a readable explanation. Message wording and localization do not need to change the policy.
- Membership and enumeration answer different questions
A membership query checks a fully specified fact. To retrieve the codes present in a result, enumerate denial facts as shown in the audit level.