Skip to content

canary-apt Part B: drive the §7 functional gates through the real pkgops issuer - #76

Merged
TroyHernandez merged 3 commits into
masterfrom
canary-apt-pkgops-issuer
Aug 20, 2026
Merged

TroyHernandez merged 3 commits into
masterfrom
canary-apt-pkgops-issuer

Conversation

@TroyHernandez

Copy link
Copy Markdown
Contributor

HELD draft — no VM run until this and the pkgops fix (#9) are reviewed and green. No provisioning, no troy-g5 activity.

Implements Part B of docs/pkgops-vm-gate-plan.md: the disposable-VM canary now drives the §7 functional gates through the real pkgops public path instead of the rab-exercise oracle. This is a harness/CI/docs change only — deploy/ is .Rbuildignored and no runix package code changes, so no version bump.

The launcher

apt-issue (apt-issue.sh → apt-issue.R, VM-only, never packaged) runs unprivileged as aptbot:

  • recomputes the preview itself via pkgops::apt_<verb>_preview();
  • treats the caller-supplied resource/plan_hash as expected values, compared byte-for-byte before any commit (never trusts caller hash data);
  • commits via pkgops::apt_<verb>(preview);
  • prints one RESULT line in the rab-exercise grammar with the same exit codes (0 persisted / 1 pre-intent / 3 left-open), so every gate assertion is unchanged;
  • no receipt or binding ever enters argv / env / disk / output.

The split (all assertions preserved)

  • Functional gates → real pkgops (apt-issue). G9 (protected) and G-OWN (not-owned) surface as pkgops preview-side refusals (apt_<verb>_preview() raises before any intent opens → effect_issued=false, outcome=preview_refused, exit 0). They are not broker-redemption refusals.
  • G12/G13/G14 (bad / replayed / stale receipt) and G15 (a package arg to the nullary apt.update entrypoint) → rab-exercise directly: inputs the pkgops issuer would never construct.
  • G11a/G11b (entrypoint isolation) → pkexec directly.

Supporting changes

  • install-apt-stack.sh: installs the R stack in the guest (R 4.6 + janssonr + pkgstate + runix + pkgops from staged sources) + the apt-issue launcher, with verify-surface checks.
  • build-and-stage.sh: git archives the three R sources; now refuses unless five trees are clean (broker, pkgexec, runix, pkgstate, pkgops).
  • redact.jq: projects the Part A durable post-state fields (observed/changed/state_changed/observed_failed/authorized_via) so the durable-record round-trip is visible in evidence; secrets still never leave the guest.
  • CI canary-apt-payload: now also R-parses deploy/canary-apt/*.R (R CMD check never sees apt-issue.R since deploy/ is .Rbuildignored).

Dependency

Installs pkgops from the #9 + Part A branch (the exported apt_<verb>() commit API lives only on #9). #9 carries the .ensure_cid fix so a mid-flight kill (G-INT) yields outcome=open/exit 3 with the session cid attached.

Depends on: cornball-ai/pkgops#9

… issuer

Swap the §7 functional gates from the rab-exercise oracle to the real pkgops
public path, per docs/pkgops-vm-gate-plan.md Part B. A thin VM-only launcher
apt-issue (apt-issue.sh -> apt-issue.R) recomputes the preview via
pkgops::apt_<verb>_preview(), compares the caller-supplied resource/plan_hash
byte-for-byte (never trusting caller hash data), commits via pkgops::apt_<verb>(),
and prints one RESULT line in the rab-exercise grammar with matching exit codes
(0 persisted / 1 pre-intent / 3 left-open), so every gate assertion is unchanged.

The split is deliberate and preserves all assertions:
- functional gates run through real pkgops (apt-issue). G9/G-OWN surface as
  pkgops PREVIEW-SIDE refusals (no intent opened; outcome=preview_refused),
  not broker-redemption refusals.
- G12/G13/G14 (bad/replayed/stale receipt injection) and G15 (a package arg to
  the nullary apt.update entrypoint) keep calling rab-exercise directly: they
  inject inputs the pkgops issuer would never construct.
- G11a/G11b call pkexec directly (entrypoint-isolation, below both).

install-apt-stack.sh now installs the R stack in the guest (R 4.6 + janssonr +
pkgstate + runix + pkgops from staged sources) and the apt-issue launcher, with
verify-surface checks. build-and-stage.sh git-archives the three R sources from
five clean pinned trees. redact.jq projects the Part A durable post-state fields
(observed/changed/state_changed/observed_failed/authorized_via) so the
durable-record round-trip is visible; secrets still never leave the guest.

CI: the canary-apt-payload job now also R-parses deploy/canary-apt/*.R
(deploy/ is .Rbuildignore'd, so R CMD check never sees apt-issue.R).
…er statuses

At PREVIEW time only package_not_owned / held / protected_package are genuine
planner policy refusals (the statuses the contract pins a plan digest to besides
ok), so only those emit outcome=preview_refused / exit 0. Previously the launcher
tested the full commit-time CLOSED set, wrongly reporting resolve_failed,
internal, and commit-only statuses as persisted refusals despite no intent being
opened. Any non-refusal condition at preview time now has no committable intent
and exits as a pre-intent failure (exit 1), matching the rab-exercise grammar.
@TroyHernandez

Copy link
Copy Markdown
Contributor Author

Part B authoritative run 4 drove through this harness (real pkgops::apt_<verb>() issuer): polkit matrix 23/23, §7 gates 68/68, rc=0. Teardown independently verified each run.

@TroyHernandez
TroyHernandez merged commit 1a076b1 into master Aug 20, 2026
6 checks passed
@TroyHernandez
TroyHernandez deleted the canary-apt-pkgops-issuer branch August 20, 2026 18:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant