3b increment 4b: wire polkit authorization into .commit_session - #6
Merged
Merged
Conversation
Wire step 2 (contract 4.3) into the orchestrator: after the capability
negotiation and before the effect intent is opened, .commit_session now
runs .authorize(verb_spec, interactive) and branches.
authorized -> proceed to the effect intent (existing path)
unauthorized/approval_required (machine mode) -> .refuse(): open a PLAIN
intent via the seam's `refuse` op (runix::broker_audit_sink +
audit_two_phase with a no-op effect) and write the terminal outcome
(effect_issued = FALSE) under one broker cid, then signal
runix_unauthorized / runix_approval_required. No effect intent is ever
opened for a refusal, so no unused effect receipt is minted (4.4).
check_failed -> fail closed with NOTHING recorded
(pkgops_polkit_check_failed): there is no authoritative decision to
persist. If the refusal RECORD itself fails, that error propagates.
`interactive` is a caller-supplied parameter (runix exposes no TTY probe, so
mode detection stays at the CLI layer); default FALSE = machine mode.
Supporting pieces:
- .verb_spec_for() (verbs.R): recover the verb spec (for the polkit action)
from a preview's request verb; a hand-built preview with an unknown verb is
refused before anything opens.
- the `refuse` op on the session-ops seam (session_ops.R), so the plain-intent
path is hermetic too.
- the approval_required -> runix_approval_required outcome status
(.PKGOPS_POLKIT_CONDITION, outcome.R); unauthorized is shared with the
session-level channel.
27 new tests (433 total): each decision drives the branch; a refusal records a
plain intent and never opens the effect intent; capability precedes the
decision; interactive skips pkcheck; a failed refuse record propagates;
autonomous update proceeds for a member.
The effect-session orchestration, outcome-closed-before-signal, and the
internal-only boundary are unchanged. Still deferred: pkgstate verification,
then the exported apt_<verb>() API.
…id cid
.refuse() accepted ops$refuse()'s result as a closed terminal outcome
whenever it carried a scalar correlation_id, without requiring durable
persistence or validating the cid grammar. audit_two_phase can return
audit_persisted=FALSE (e.g. the terminal outcome did not land) WITHOUT
raising, so a non-persisted or malformed refusal could be signaled as a
clean runix_unauthorized / runix_approval_required -- claiming an audit
that never closed.
Before signaling the refusal, require res$audit_persisted == TRUE AND a
valid broker cid (.valid_broker_cid, pinned to runix's .BROKER_CID_RE:
^[0-9]{20}-[0-9a-f]{16}$). Otherwise fail closed as runix_broker_error and
do not report the refusal as closed. Regression tests: audit_persisted
FALSE, a malformed cid, a missing audit_persisted field, and the cid
grammar itself.
Contributor
Author
|
Fixed in the latest commit. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Wires the polkit authorization decision (increment 4a) into the commit
orchestrator — contract §4.3 step 2, including the plain-intent terminal refusal
path. Still hermetic (the broker,
pkexec/pkcheck, and dpkg are all behindseams) and still internal:
pkgstateverification and the exported API remain.The branch
Step 2 runs
.authorize(verb_spec, interactive)after the capabilitynegotiation and before the effect intent is opened:
authorizedunauthorized/approval_required(machine mode).refuse(): open a plain intent (no receipt) via the seam'srefuseop and write the terminal outcome (effect_issued = FALSE) under one broker cid, then signalrunix_unauthorized/runix_approval_required. No effect intent is ever opened for a refusal, so no unused effect receipt is minted (§4.4).check_failedpkgops_polkit_check_failed) — there is no authoritative decision to persistThe refusal record is the "close" that precedes the signal, so a refused attempt
is durably audited. If the refusal record itself cannot be written, that error
propagates — either way no effect ran.
interactiveis a caller-supplied parameter (runix exposes no TTY probe, somode detection stays at the CLI layer); default
FALSE= machine mode.Supporting pieces
.verb_spec_for()(verbs.R): recover the verb spec (for the polkitaction) from a preview's request verb; a hand-built preview with an unknown verb
is refused before anything opens.
refuseop on the session-ops seam (session_ops.R) =broker_audit_sink()audit_two_phase()with a no-opeffectand a terminal-outcome record; keepsthe plain-intent path hermetic. (Confirmed against the runix API: this is the
exported plain-intent open+write pair, root-peer-authenticated, one cid.)
approval_required→runix_approval_requiredoutcome status(
.PKGOPS_POLKIT_CONDITION,outcome.R);unauthorizedis shared with thesession-level channel (same status, same condition).
Tests
27 new (433 total): each decision drives the branch; a refusal records a plain
intent and never opens the effect intent (
seq == c("capability","refuse"));capability precedes the decision; interactive skips
pkcheck; a failed refuserecord propagates; an autonomous
updateproceeds for a member.Points for review
§4.3 — so even a refusal first negotiates the effect-receipt capability. It's
contract-ordered and confirms the broker is reachable before anything; flag if
you'd rather the refuse path skip capability.
check_failed→pkgops_polkit_check_failed, nothing recorded — apkgops-owned outcome the contract taxonomy doesn't name (like
pkgops_spawn_failed). Opening no intent for a check that couldn't run seemedright (no authoritative decision to persist), but flagging the choice.
operation/resource/effect_issued/outcome) is minimal, like.outcome_record()— pinnedagainst a real broker in the VM-gated increment.
CI hermetic;
R CMD checkclean but for the expected "New submission" WARNING.