06 — Self-Approval Guard & Null-Proposer Risk Repair
06 — Self-approval guard & null-proposer risk repair (design)
No vote on APR-0415 was recorded. This file specifies how dot-apr-approve's G5 must behave; it does not act.
Live fact: APR-0415 has no recorded proposer
approval_requests.code = APR-0415
source = 'dot-c1-w7-authorize-build-step-handler-proposal'
source_context (json) = {macro, scope, dot_code, file_path, patch_mode, version_bump,
live_sha256, patched_sha256, base64_sha256, bash_n, change,
binds_handler_ref:false, mints_grant:false, registers_dot:false, kb_package}
source_context.proposer = ABSENT (NULL)
source_context.created_by = ABSENT (NULL)
reviewed_by = NULL
Why this matters — the existing guards go toothless on NULL proposer
Both quorum routines exclude the proposer via COALESCE(source_context->>'proposer', source_context->>'created_by'):
quorum_passed(p_code): counts votesWHERE (v_proposer IS NULL OR approver <> v_proposer)— when proposer is NULL the predicate is always true ⇒ no exclusion happens.fn_apr_quorum_check()trigger: self-approve block runs onlyIF v_proposer IS NOT NULL⇒ skipped here.
So for APR-0415, the platform's self-approval protection is inert. A dot-apr-approve that merely trusted the
DB guards would let the proposer self-approve undetected. G5 must be stricter than the DB.
G5 repair (fail-closed, in the tool)
1. Resolve proposer from EVERY available signal, not just source_context.proposer:
source_context.proposer, source_context.created_by, source_context.actor/agent,
approval_requests.source, approval_requests.reviewed_by, and any request creator/actor field.
2. If a proposer identity IS resolved:
reject if --approver matches it (case-insensitive / normalized). (true self-approval block)
3. If proposer is UNKNOWABLE (all signals NULL, as in APR-0415):
DO NOT silently pass. Require an explicit president/council OVERRIDE rationale AND set a
`self_approval_risk=true` flag on the audit row. The vote is recorded only with that explicit,
logged acknowledgement; absent it ⇒ reject (fail-closed).
4. Never infer "safe" from absence of data. Absence ⇒ risk, not permission.
This makes the null-proposer hole visible and gated, instead of an invisible bypass. It does not "fix" APR-0415 (which is out of scope and untouched); it ensures the future channel cannot be exploited through that hole.
Note on scope
G5 here governs dot-apr-approve only. Hardening the DB functions themselves (e.g. requiring a non-NULL proposer
at propose-time, or recording proposer provenance) is a separate governance change, not part of this minimal
bootstrap, and is flagged as a follow-up — not performed.