Profile permissions belong in one table, because scattered they hid a capability chat was never meant to have

This commit is contained in:
Gmer4Lfe
2026-08-06 20:51:19 -04:00
parent d562644581
commit 917ff43795
3 changed files with 73 additions and 17 deletions
+52
View File
@@ -82,6 +82,58 @@ require_once __DIR__ . '/config.php';
define('VV_AI_JOB_DIR', '/tmp/varaverk_ai_jobs');
const VV_AI_KINDS = ['header', 'readme', 'manual', 'template', 'doc'];
// ── What each profile is allowed to see and do ───────────────────────────────────────────────
// A profile is a contract plus a set of inputs, and the inputs are the half that has to be
// enforced rather than requested. This table is that half, in one place.
//
// It exists because the alternative already failed. The same permissions used to live as a dozen
// `$profile === 'varaverk' || $profile === 'troubleshoot'` conditions spread across the worker,
// and answering "may chat ever be shown a log?" meant reading all of them. It could — a gate
// added for run-outcome questions granted it by omission, and the chat profile, whose entire
// value is that it has NOT been shown this installation, was one phrasing away from being handed
// a health sweep and 120 lines of log. Nothing about that was visible at the point of the
// mistake. Here it would have been one missing word on one line.
//
// A capability is permission, not need. varaverk holds 'health' but only attaches it when the
// question looks diagnostic; troubleshoot attaches it always. The gates decide whether an input
// is warranted, this decides whether it is allowed, and a gate can never widen the grant.
//
// The ordering is deliberate: chat holds nothing, and that emptiness is a guarantee, not an
// oversight. Anything added to it stops being general chat and becomes an assistant that
// sometimes lies about this installation.
const VV_AI_CAPS = [
// retrieval passages from the index, and the kind filter the page exposes for them
'retrieve' => ['varaverk', 'troubleshoot'],
'kind_filter' => ['varaverk'],
// live health sweep measured at question time
'health' => ['varaverk', 'troubleshoot'],
// run record + log tail for a script named in the question
'run_evidence' => ['varaverk', 'troubleshoot'],
// log tail for whatever the operator currently has open
'scoped_log' => ['troubleshoot'],
// operator-written history of what previously went wrong with this thing
'incidents' => ['varaverk', 'troubleshoot'],
// deterministic "where does this conf key actually live" lookup
'conf_lookup' => ['varaverk', 'troubleshoot'],
// may file a bug report against Varaverk itself
'file_bugs' => ['troubleshoot'],
// destructive-operation scan of generated shell
'code_scan' => ['code'],
];
function vv_ai_profile_can(string $profile, string $cap): bool {
return in_array($profile, VV_AI_CAPS[$cap] ?? [], true);
}
// Whether a General Chat message is really about this installation. Shared by the deterministic
// backstop and the handoff, so both agree by construction: a question the backstop would have
// refused is exactly the one the handoff should escalate, and drift between the two would leave
// a class of question that is neither answered nor deferred.
function vv_ai_chat_needs_varaverk(string $question): bool {
return vv_ai_mentions_varaverk($question)
|| (bool)preg_match('/\b[\w.-]+\.sh\b|\b[A-Z][A-Z0-9]*(_[A-Z0-9]+)+\b/', $question);
}
function vv_ai_config(): array {
static $cfg = null;
if ($cfg !== null) return $cfg;