← Indice documentazione Guida all'architettura › policy

Metnos

policy — registro delle capacità e concessioni
Guida al modello dichiarativo di autorizzazione

Chiedi a Metnos con una richiesta come quella di questo esempio: «Spiegami quali controlli si applicano se chiedo di leggere un file e quali se chiedo di modificarlo». La risposta deve distinguere i controlli attivi del runtime dalla matrice dichiarativa descritta in questa pagina.

runtime/policy.py offre un registro canonico, una matrice deterministica dei livelli di autonomia e un archivio di concessioni circoscritte a un obiettivo. Queste API sono disponibili e ispezionabili, ma non costituiscono ancora il controllo generale del percorso ordinario di esecuzione. Le autorizzazioni effettive del runtime sono descritte nei documenti dei componenti che le applicano.

Indice

  1. Cos'è policy
  2. Registro delle capability
  3. Tabella autonomia × capability
  4. Concessioni persistenti per bersaglio
  5. Esito combinato (effective_outcome)
  6. Integrazione runtime
  7. CLI
  8. Come verificare il modulo
  9. Confini del modulo

1. Cos'è policy

Il modulo policy descrive un possibile esito di autorizzazione per una tripla composta da livello di autonomia, capacità e obiettivo. L'esito può essere consentito, negato oppure approvazione richiesta. È un modello deterministico disponibile nel codice; non va confuso con un controllo già applicato a ogni executor.

capability richiesta dall'executor livello autonomia ReadOnly/Supervised/Full grant per_target persistenti effective_outcome consenti / chiedi / nega
Figura 1 — Il calcolo esposto dal modulo: capacità, livello di autonomia e concessione circoscritta producono un esito.

Il modello distingue tre responsabilità, affidate a componenti diversi:

Questa separazione è il disegno del sottosistema, non una dichiarazione sull'ordine del percorso attuale. Nel codice corrente il registro viene usato per convalidare le capacità dichiarate dai manifest; la chiamata generale a effective_outcome prima di ogni esecuzione non è ancora presente.

Contratto dichiarativo. I manifest attivi dichiarano capacità appartenenti al registro canonico. I controlli effettivi restano distribuiti fra convalida del manifest, vaglio, dialoghi di approvazione, sandbox e regole specifiche del dispatcher; questa pagina non li attribuisce tutti a policy.py.

2. Registro delle capacità

Il registro è il dizionario chiuso delle capacità riconosciute dal validatore dei manifest. Le voci canoniche sono definite in runtime/policy.py:CAPABILITY_REGISTRY. Ogni voce è un CapabilitySpec con quattro attributi:

La tabella seguente mostra un gruppo rappresentativo. Il registro nel codice e l'uscita del comando registry sono le fonti complete e aggiornate.

namecriticaldefault_approvaltarget_kinddescrizione
compute:purenononenonecalcolo deterministico in memoria senza I/O esterno
fs:readnoper_targetpath_globlettura file dal filesystem locale entro path_glob dichiarati
fs:writeper_targetpath_globscrittura/modifica file entro path_glob (critica)
code:execalwaysexactesecuzione di un comando shell di una lista bianca (es. gestore di pacchetti)
network:httpnoper_targethostHTTP/HTTPS GET/POST verso host autorizzati
llm:localnononenonechiamata LLM locale (llama-server, llama.cpp), costo zero
llm:onlinenoper_targetnonechiamata LLM online (Anthropic, OpenAI,...), costo > 0
mail:readnoper_targetexactlettura di messaggi IMAP da una casella di posta autorizzata
mail:sendalwaysexactinvio SMTP a destinatari (irreversibile, alta posta in gioco)
channel:innononeexactricezione messaggi da un canale (Telegram, CLI, voice)
channel:outnoper_targetexactinvio messaggi a un canale specifico
time:readnononenonelettura ora corrente e fusi orari
parse:localnononenoneparsing locale di formati noti (PDF, HTML, JSON, CSV)
calendar:readnoper_targetexactlettura eventi da un calendario autorizzato
index:readnoper_targetexactlettura di un indice locale amministrato da Metnos
metnos:readnoper_targetexactlettura di una risorsa locale amministrata da Metnos
metnos:cachenononenonecache tecnica best-effort in un sottoalbero amministrato da Metnos, senza effetti sui dati utente
system:readnoper_targetexactlettura diagnostica di sistema su host o dispositivo autorizzato
provider:accessnoper_targetexactaccesso alla rete e alle credenziali di un fornitore autorizzato

Il registro è chiuso: record_grant rifiuta una capacità che non vi appartiene. Aggiungerne una significa modificare il registro nel codice e convalidare i manifest — non esiste una registrazione dinamica durante l'esecuzione. Il vocabolario delle azioni è quindi un contratto controllato.

provider:access usa come bersaglio esatto il binding della skill. Una clausola when nel manifest può restringerla a un backend della singola invocazione; la policy non ricava mai questo permesso dal nome dell'executor o da un valore libero del piano.

2.1 Come leggere il registro

Il registro comprende letture, scritture, accessi a fornitori esterni, operazioni amministrative e interazioni umane. Il flag critical e la classe default_approval contribuiscono alla matrice del modulo. Non dimostrano, da soli, che una richiesta di conferma sia presentata nel percorso di esecuzione corrente.

Per esempio, compute:pure e time:read hanno classe none; fs:write ha classe per_target; mail:send, mail:write, system:admin e drive:permissions hanno classe always. L'elenco completo va letto dal registro, non mantenuto a mano in questa pagina.

3. Tabella autonomia × capability

La matrice è il prodotto dei tre livelli di autonomia (ReadOnly, Supervised, Full) per le capacità registrate. Per ogni cella produce un esito: allowed, approval_required o denied. È generata da _init_table. Qui sotto compare un estratto; il comando table stampa la matrice completa.

capabilityReadOnlySupervisedFull
compute:pureallowedallowedallowed
fs:readapprovalapprovalallowed
fs:writedeniedapprovalallowed
code:execdeniedapprovalapproval
network:httpdeniedapprovalallowed
llm:localallowedallowedallowed
llm:onlinedeniedapprovalallowed
mail:readapprovalapprovalallowed
mail:senddeniedapprovalapproval
channel:inallowedallowedallowed
channel:outdeniedapprovalallowed
time:readallowedallowedallowed
parse:localallowedallowedallowed
calendar:readapprovalapprovalallowed
index:readapprovalapprovalallowed
metnos:readapprovalapprovalallowed
metnos:cacheallowedallowedallowed
system:readapprovalapprovalallowed
provider:accessapprovalapprovalallowed

3.1 Le tre regole che generano la tabella

La tabella non è arbitraria: nasce da tre regole, una per livello, che _init_table applica iterando sul registro.

ReadOnly. Le capacità non critiche con classe none sono consentite. Un insieme esplicito di letture e accessi circoscritti — fra cui fs:read, mail:read, calendar:read, network:sites e provider:access — produce approval_required. Le altre capacità critiche o con approvazione predefinita vengono negate.

Supervised. Le capacità con classe none sono consentite; tutte le altre producono approval_required. Questa è la semantica della matrice, non la prova che il dispatcher corrente presenti il relativo dialogo.

Full. Le capacità con classe always producono ancora approval_required; tutte le altre sono consentite. Fra le voci correnti con classe always figurano code:exec, mail:send, mail:write, system:admin e drive:permissions.

Un esito non equivale a un controllo applicato. La matrice conserva approval_required per le classi always anche al livello Full. Finché il dispatcher non usa questo esito come vincolo generale, la proprietà va letta come contratto del modulo e non come garanzia dell'intero sistema.

La reversibilità in pratica: il motore di undo

La reversibilità è un contratto distinto dalla matrice di autonomia. Un executor conforme allo standard che dichiara revertible = true deve indicare un reverse_pattern. Il runtime conserva i dati di esecuzione necessari e undo_last_turn prova ad applicare il percorso inverso, riferendo anche gli eventuali fallimenti.

Il catalogo chiuso in runtime/reverse_patterns.py comprende, fra gli altri:

Per i manifest conformi allo standard, il validatore controlla la coerenza fra la dichiarazione di reversibilità e il pattern inverso. Restano percorsi di compatibilità per alcuni executor manuali: per questo non è corretto affermare che ogni operazione modificatrice usi soltanto il catalogo.

La presenza di un pattern inverso non concede autorità e non elimina, da sola, un'eventuale approvazione. Reversibilità, autorizzazione e isolamento sono controlli separati.

4. Concessioni persistenti per bersaglio

Il modulo offre anche un archivio SQLite per concessioni circoscritte. Ogni record lega un canale, un mittente, una capacità, un obiettivo e un'eventuale scadenza. L'archivio esiste ed è interrogabile; il percorso ordinario di approvazione non vi scrive ancora concessioni.

4.1 Schema SQLite

Lo schema è definito da runtime/policy.py:SCHEMA:

CREATE TABLE IF NOT EXISTS grants (
 id INTEGER PRIMARY KEY AUTOINCREMENT,
 channel TEXT NOT NULL,
 sender_id TEXT NOT NULL,
 capability TEXT NOT NULL,
 target TEXT NOT NULL,
 granted_at TEXT NOT NULL,
 expires_at TEXT,
 granted_by TEXT,
 revoked_at TEXT
);

Una concessione è identificata dalla quaterna (channel, sender_id, capability, target). Le date granted_at, expires_at e revoked_at sono espresse in UTC. La separazione logica fra utenti dipende dal corretto passaggio di canale e mittente da parte del chiamante.

Il file predefinito è ~/.local/state/metnos/grants.db per l'utente di installazione; METNOS_GRANTS_DB permette di indicarne un altro. Più utenti applicativi serviti dallo stesso processo condividono dunque il contenitore fisico, mentre le righe restano distinte dall'identità fornita all'API.

4.2 API

funzionecosa fa
record_grant(channel, sender_id, capability, target, expires_at=None, granted_by=None) Registra una concessione. Solleva ValueError se la capability non è nel registro. Ritorna l'oggetto Grant con id assegnato.
has_grant(channel, sender_id, capability, target) True se esiste un grant attivo (non revocato, non scaduto) per la quaterna. La query confronta expires_at con il tempo corrente.
list_grants(channel=None, sender_id=None, include_revoked=False) Elenca le concessioni, filtrabili per canale e mittente; può includere quelle revocate. L'ordine è decrescente per granted_at.
revoke_grant(grant_id) Pone revoked_at al momento corrente. Ritorna True se è stato modificato qualcosa, False se il grant era già revocato o non esisteva.

Le funzioni aprono e chiudono una connessione per chiamata; non mantengono una sessione SQLite globale.

4.3 Stato dell'integrazione

Nel runtime corrente non risultano chiamanti di produzione di record_grant e il pianificatore non consulta effective_outcome. La tabella può essere popolata chiamando direttamente l'API del modulo, ma non esiste una superficie amministrativa attiva che lo faccia e non va descritta come la memoria delle conferme dell'utente.

5. Esito combinato (effective_outcome)

La funzione effective_outcome combina la matrice e le concessioni in un unico esito. È disponibile ai chiamanti del modulo e alla CLI; non è attualmente il punto di accesso del pianificatore.

tabella dicegrant attivo per (channel, sender, target)?esito
allowedindifferente, non si interroga il DBallowed
deniedindifferente, non si interroga il DBdenied
approval_requiredallowed
approval_requiredno (oppure parametri di scope mancanti)approval_required

La logica è lineare: se la tabella decide già in modo netto (allowed o denied), il grant non viene neppure consultato; se decide approval_required, un grant attivo lo trasforma in allowed, altrimenti resta approval_required.

Invariante della funzione: una concessione non trasforma mai denied in allowed. Può invece trasformare approval_required in allowed.

Limite da conoscere. effective_outcome non distingue fra per_target e always quando trova una concessione attiva. Anche una capacità classificata always verrebbe quindi elevata ad allowed se qualcuno ne registrasse una concessione. Prima di collegare il modulo al dispatcher occorre impedire questo caso oppure definire esplicitamente una semantica diversa.

5.1 Esempio verificabile

Valutazione del modulo, non esecuzione di un turno. Il comando check Supervised fs:write, senza identità e obiettivo, restituisce approval_required. Il comando check ReadOnly fs:write restituisce denied. Questi risultati mostrano la matrice; non salvano un file e non aprono un dialogo nella chat.

6. Integrazione runtime

Il modulo partecipa al runtime nei punti seguenti:

Di conseguenza questo archivio di concessioni non va interpretato come il meccanismo di autorizzazione attivo del pianificatore. Un eventuale chiamante deve conservare identità e obiettivo esatti, rispettare l'isolamento per utente, gestire correttamente le classi always e usare un renderer semantico localizzato.

7. CLI

Dal percorso principale del repository, il modulo è ispezionabile con l'ambiente di Metnos. Espone cinque sottocomandi.

comandocosa fa
PYTHONPATH=runtime ./.venv/bin/python -m policy registrystampa una riga JSON per ogni capacità con tutti i suoi attributi.
PYTHONPATH=runtime ./.venv/bin/python -m policy tablestampa una riga JSON per ogni livello con l'esito di tutte le capacità.
PYTHONPATH=runtime ./.venv/bin/python -m policy check <level> <capability> [--channel C --sender S --target T]stampa l'esito di effective_outcome. Senza identità e obiettivo mostra l'esito della sola matrice.
PYTHONPATH=runtime ./.venv/bin/python -m policy grants [--channel C] [--sender S] [--all]elenca le concessioni attive; --all include quelle revocate.
PYTHONPATH=runtime ./.venv/bin/python -m policy revoke <grant_id>revoca una concessione per identificatore.

I comandi registry, table e grants producono JSON Lines e possono essere filtrati con jq.

8. Come verificare il modulo

La verifica del modulo comprende:

9. Confini della policy

Note finali

Il valore attuale del modulo è circoscritto ma concreto: registro chiuso, matrice deterministica e archivio strutturato. La garanzia pubblica resta limitata a ciò che il runtime collega davvero.

Presentare questa API come filtro generale prima del collegamento al dispatcher sarebbe un'affermazione più ampia del comportamento dimostrato.