@evoclock/pi-agentic-driver 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,366 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ phase=setup
5
+ if [ "$#" -ne 2 ]; then
6
+ printf 'microvm failure phase=identity code=2 detail=fixture id and script hash are required\n' >&2
7
+ exit 2
8
+ fi
9
+ fixture_id=$1
10
+ script_hash=$2
11
+ fixture_fail() {
12
+ local code=$1
13
+ shift
14
+ printf 'microvm failure phase=%s code=%s detail=%s\n' "$phase" "$code" "$*" >&2
15
+ exit "$code"
16
+ }
17
+ case "$fixture_id" in
18
+ ""|*[!A-Za-z0-9._-]*) fixture_fail 2 'unsafe fixture id' ;;
19
+ esac
20
+ if [ "${#script_hash}" -ne 64 ]; then fixture_fail 2 'invalid fixture script hash'; fi
21
+ case "$script_hash" in
22
+ ""|*[!0-9a-f]*) fixture_fail 2 'invalid fixture script hash' ;;
23
+ esac
24
+
25
+ state_root="$HOME/agentic-driver-state/cutover-fixtures/microvm"
26
+ fixture_root="$state_root/$fixture_id"
27
+ domain="agentic-driver-$fixture_id"
28
+ kernel="/boot/vmlinuz-$(uname -r)"
29
+ marker="AGENTIC_MICROVM_PROBE:$fixture_id"
30
+ acl_backup="$fixture_root/home.acl.before"
31
+ acl_after="$fixture_root/home.acl.after"
32
+ initramfs="$fixture_root/initramfs.cpio.gz"
33
+ initramfs_acl_after="$fixture_root/initramfs.acl.after"
34
+ initramfs_build_log="$fixture_root/initramfs.build.log"
35
+ console_error="$fixture_root/console.stderr"
36
+ receipt_schema="agentic-driver.linux-microvm-cutover.v1"
37
+
38
+ mkdir -p "$state_root" || fixture_fail 3 'fixture state root could not be created'
39
+ if ! mkdir "$fixture_root"; then
40
+ fixture_fail 3 'fixture already exists'
41
+ fi
42
+ if ! getfacl -p "$HOME" >"$acl_backup"; then
43
+ fixture_fail 3 'home ACL snapshot could not be captured'
44
+ fi
45
+ home_acl_applied=false
46
+ initramfs_acl_applied=false
47
+ domain_started=false
48
+ domain_destroy_requested=false
49
+ recorder_pid=""
50
+ acl_restored=false
51
+ domain_absent=false
52
+
53
+ if ! home_acl_before_sha=$(sha256sum "$acl_backup" | awk '{print $1}'); then
54
+ fixture_fail 3 'home ACL snapshot digest could not be computed'
55
+ fi
56
+
57
+ cleanup_failed=false
58
+ cleanup_error_count=0
59
+ cleanup_error() {
60
+ cleanup_failed=true
61
+ cleanup_error_count=$((cleanup_error_count + 1))
62
+ local detail=${1:-'unspecified cleanup failure'}
63
+ detail=$(printf '%s' "$detail" | tr '\r\n' ' ' | cut -c1-400)
64
+ printf 'microvm cleanup failure code=cleanup-failed detail=%s\n' "$detail" >&2
65
+ }
66
+ run_cleanup() {
67
+ local label=$1
68
+ shift
69
+ local output status
70
+ output=$("$@" 2>&1)
71
+ status=$?
72
+ if [ "$status" -ne 0 ]; then
73
+ cleanup_error "$label exit=$status ${output:-no diagnostic}"
74
+ fi
75
+ return "$status"
76
+ }
77
+
78
+ domain_state() {
79
+ local names
80
+ if virsh dominfo "$domain" >/dev/null 2>&1; then
81
+ printf 'present'
82
+ return 0
83
+ fi
84
+ if ! names=$(virsh list --all --name 2>&1); then
85
+ printf 'domain absence query failed: %s\n' "${names:-no diagnostic}" >&2
86
+ return 2
87
+ fi
88
+ if grep -F -x -- "$domain" <<<"$names" >/dev/null; then
89
+ printf 'present'
90
+ else
91
+ local match_status=$?
92
+ if [ "$match_status" -eq 1 ]; then
93
+ printf 'absent'
94
+ else
95
+ printf 'domain name absence query failed\n' >&2
96
+ return 2
97
+ fi
98
+ fi
99
+ }
100
+
101
+ assert_domain_absent() {
102
+ local state
103
+ if ! state=$(domain_state); then return 2; fi
104
+ if [ "$state" != absent ]; then return 1; fi
105
+ return 0
106
+ }
107
+
108
+ verify_initramfs_acl_removed() {
109
+ if ! getfacl -p "$initramfs" >"$initramfs_acl_after"; then return 1; fi
110
+ if grep -F 'user:libvirt-qemu:' "$initramfs_acl_after" >/dev/null; then
111
+ return 1
112
+ else
113
+ local match_status=$?
114
+ if [ "$match_status" -eq 1 ]; then return 0; fi
115
+ return 2
116
+ fi
117
+ }
118
+
119
+ cleanup_recorder() {
120
+ if [ -z "$recorder_pid" ]; then
121
+ printf 'microvm cleanup recorder=no-op\n' >&2
122
+ return
123
+ fi
124
+ if kill -0 "$recorder_pid" 2>/dev/null; then
125
+ if run_cleanup 'stop console recorder' kill "$recorder_pid"; then
126
+ printf 'microvm cleanup recorder=terminated\n' >&2
127
+ fi
128
+ else
129
+ printf 'microvm cleanup recorder=no-op\n' >&2
130
+ fi
131
+ }
132
+
133
+ cleanup_domain() {
134
+ local state
135
+ if [ "$domain_started" != true ]; then
136
+ printf 'microvm cleanup domain=no-op\n' >&2
137
+ return
138
+ fi
139
+ if state=$(domain_state); then
140
+ if [ "$state" = present ]; then
141
+ domain_destroy_requested=true
142
+ run_cleanup "destroy domain $domain" virsh destroy "$domain"
143
+ fi
144
+ else
145
+ cleanup_error 'domain absence query failed during cleanup'
146
+ fi
147
+ if state=$(domain_state); then
148
+ if [ "$state" = absent ]; then
149
+ printf 'microvm cleanup domain=checked-absent\n' >&2
150
+ else
151
+ cleanup_error "domain $domain remains present after cleanup"
152
+ fi
153
+ else
154
+ cleanup_error 'final domain absence query failed during cleanup'
155
+ fi
156
+ }
157
+
158
+ cleanup_acl() {
159
+ if [ "$initramfs_acl_applied" = true ]; then
160
+ if [ -e "$initramfs" ]; then
161
+ run_cleanup 'remove initramfs ACL' setfacl -x u:libvirt-qemu "$initramfs"
162
+ if verify_initramfs_acl_removed; then
163
+ initramfs_acl_applied=false
164
+ else
165
+ cleanup_error 'initramfs ACL removal could not be verified'
166
+ fi
167
+ else
168
+ printf 'microvm cleanup initramfs_acl=no-op-target-absent\n' >&2
169
+ initramfs_acl_applied=false
170
+ fi
171
+ else
172
+ printf 'microvm cleanup initramfs_acl=no-op\n' >&2
173
+ fi
174
+
175
+ if [ "$home_acl_applied" = true ]; then
176
+ run_cleanup 'restore home ACL' setfacl --restore="$acl_backup"
177
+ if getfacl -p "$HOME" >"$acl_after"; then
178
+ if cmp -s "$acl_backup" "$acl_after"; then
179
+ if home_acl_after_sha=$(sha256sum "$acl_after" | awk '{print $1}'); then
180
+ home_acl_applied=false
181
+ acl_restored=true
182
+ else
183
+ cleanup_error 'restored home ACL digest could not be computed'
184
+ fi
185
+ else
186
+ cleanup_error 'restored home ACL differs from home.acl.before'
187
+ fi
188
+ else
189
+ cleanup_error 'restored home ACL could not be captured'
190
+ fi
191
+ else
192
+ printf 'microvm cleanup home_acl=no-op\n' >&2
193
+ fi
194
+ }
195
+
196
+ cleanup_on_exit() {
197
+ local original_status=$?
198
+ trap - EXIT INT TERM HUP
199
+ set +e
200
+ cleanup_recorder
201
+ cleanup_domain
202
+ cleanup_acl
203
+ if [ "$cleanup_failed" = true ]; then
204
+ printf 'microvm cleanup summary=failed errors=%s\n' "$cleanup_error_count" >&2
205
+ exit 70
206
+ fi
207
+ printf 'microvm cleanup summary=clean\n' >&2
208
+ exit "$original_status"
209
+ }
210
+ trap cleanup_on_exit EXIT INT TERM HUP
211
+
212
+ phase=preflight
213
+ for tool in /usr/bin/qemu-system-x86_64 /usr/bin/busybox /usr/bin/cpio /usr/bin/gzip /usr/bin/setfacl /usr/bin/getfacl; do
214
+ if ! test -x "$tool"; then fixture_fail 4 "required tool unavailable: $tool"; fi
215
+ done
216
+ if ! test -r /dev/kvm -a -w /dev/kvm; then fixture_fail 4 '/dev/kvm is unavailable'; fi
217
+ if ! test -e "$kernel"; then fixture_fail 4 "kernel unavailable: $kernel"; fi
218
+ if state=$(domain_state); then
219
+ if [ "$state" != absent ]; then fixture_fail 5 'fixture domain already exists'; fi
220
+ else
221
+ fixture_fail 5 'fixture domain absence could not be established'
222
+ fi
223
+
224
+ phase=build
225
+ root="$fixture_root/root"
226
+ if ! mkdir -p "$root/bin" "$root/proc" "$root/sys" "$root/dev"; then fixture_fail 6 'guest root could not be created'; fi
227
+ if ! cp /usr/bin/busybox "$root/bin/busybox"; then fixture_fail 6 'BusyBox could not be copied'; fi
228
+ for name in sh mount poweroff uname; do
229
+ if ! ln -s busybox "$root/bin/$name"; then fixture_fail 6 "BusyBox link could not be created: $name"; fi
230
+ done
231
+ if ! cat >"$root/init" <<EOF
232
+ #!/bin/busybox sh
233
+ /bin/mount -t proc proc /proc
234
+ /bin/mount -t sysfs sysfs /sys
235
+ /bin/mount -t devtmpfs devtmpfs /dev
236
+ echo '$marker'
237
+ echo "guest-kernel=\$(/bin/uname -r)"
238
+ echo 'network=absent disk=absent host-share=absent'
239
+ sync
240
+ /bin/poweroff -f
241
+ EOF
242
+ then
243
+ fixture_fail 6 'guest init could not be written'
244
+ fi
245
+ if ! chmod 0755 "$root/init"; then fixture_fail 6 'guest init could not be made executable'; fi
246
+ if ! (
247
+ cd "$root"
248
+ find . -print0 | LC_ALL=C sort -z | /usr/bin/cpio --null -o --format=newc 2>"$initramfs_build_log" | /usr/bin/gzip -n >"$initramfs"
249
+ ); then
250
+ build_detail=$(tr '\r\n' ' ' <"$initramfs_build_log" | cut -c1-300)
251
+ fixture_fail 6 "initramfs could not be built${build_detail:+: $build_detail}"
252
+ fi
253
+ if ! initramfs_sha=$(sha256sum "$initramfs" | awk '{print $1}'); then
254
+ fixture_fail 6 'initramfs digest could not be computed'
255
+ fi
256
+ if [ "${#initramfs_sha}" -ne 64 ]; then fixture_fail 6 'initramfs digest is invalid'; fi
257
+
258
+ if ! cat >"$fixture_root/domain.xml" <<EOF
259
+ <domain type='kvm'>
260
+ <name>$domain</name>
261
+ <memory unit='MiB'>128</memory>
262
+ <vcpu placement='static'>1</vcpu>
263
+ <os>
264
+ <type arch='x86_64' machine='microvm'>hvm</type>
265
+ <kernel>$kernel</kernel>
266
+ <initrd>$initramfs</initrd>
267
+ <cmdline>earlycon=uart,io,0x3f8,115200 console=ttyS0,115200 rdinit=/init reboot=t panic=1</cmdline>
268
+ </os>
269
+ <on_poweroff>destroy</on_poweroff>
270
+ <on_reboot>destroy</on_reboot>
271
+ <on_crash>destroy</on_crash>
272
+ <devices>
273
+ <emulator>/usr/bin/qemu-system-x86_64</emulator>
274
+ <serial type='pty'>
275
+ <target type='isa-serial' port='0'>
276
+ <model name='isa-serial'/>
277
+ </target>
278
+ </serial>
279
+ <console type='pty'><target type='serial' port='0'/></console>
280
+ </devices>
281
+ </domain>
282
+ EOF
283
+ then
284
+ fixture_fail 6 'domain XML could not be written'
285
+ fi
286
+
287
+ phase=acl
288
+ home_acl_applied=true
289
+ if ! setfacl -m u:libvirt-qemu:--x "$HOME"; then fixture_fail 7 'home ACL could not be applied'; fi
290
+ initramfs_acl_applied=true
291
+ if ! setfacl -m u:libvirt-qemu:r "$initramfs"; then fixture_fail 7 'initramfs ACL could not be applied'; fi
292
+
293
+ phase=console
294
+ domain_started=true
295
+ script -q -e -c "virsh create '$fixture_root/domain.xml' --console" "$fixture_root/console.typescript" > /dev/null 2>"$console_error" &
296
+ recorder_pid=$!
297
+ marker_seen=false
298
+ for _attempt in $(seq 1 90); do
299
+ if grep -F "$marker" "$fixture_root/console.typescript" >/dev/null 2>&1; then
300
+ marker_seen=true
301
+ break
302
+ fi
303
+ if ! kill -0 "$recorder_pid" 2>/dev/null; then break; fi
304
+ sleep 0.5
305
+ done
306
+
307
+ phase=teardown
308
+ if ! state=$(domain_state); then fixture_fail 9 'domain teardown query failed'; fi
309
+ if [ "$state" = present ]; then
310
+ domain_destroy_requested=true
311
+ if ! virsh destroy "$domain" >/dev/null; then fixture_fail 9 "domain destroy failed: $domain"; fi
312
+ fi
313
+ recorder_status=0
314
+ if wait "$recorder_pid"; then :; else recorder_status=$?; fi
315
+ if [ "$recorder_status" -ne 0 ]; then
316
+ recorder_detail=$(tr '\r\n' ' ' <"$console_error" | cut -c1-300)
317
+ fixture_fail 9 "console recorder failed with status $recorder_status${recorder_detail:+: $recorder_detail}"
318
+ fi
319
+
320
+ phase=evidence
321
+ # util-linux script may flush its final transcript only while exiting. Re-read
322
+ # the retained recording after join so a successful late marker is not
323
+ # classified from the stale polling-loop state.
324
+ if grep -F "$marker" "$fixture_root/console.typescript" >/dev/null 2>&1; then
325
+ marker_seen=true
326
+ fi
327
+ if [ "$marker_seen" != true ]; then
328
+ fixture_fail 10 'guest marker missing from bounded console recording'
329
+ fi
330
+
331
+ phase=acl
332
+ if ! setfacl -x u:libvirt-qemu "$initramfs"; then fixture_fail 11 'initramfs ACL could not be removed'; fi
333
+ if ! verify_initramfs_acl_removed; then fixture_fail 11 'initramfs ACL removal could not be verified'; fi
334
+ initramfs_acl_applied=false
335
+ if ! setfacl --restore="$acl_backup"; then fixture_fail 11 'home ACL could not be restored'; fi
336
+ if ! getfacl -p "$HOME" >"$acl_after"; then fixture_fail 11 'restored home ACL could not be captured'; fi
337
+ if ! cmp -s "$acl_backup" "$acl_after"; then fixture_fail 11 'restored home ACL differs from home.acl.before'; fi
338
+ if ! home_acl_after_sha=$(sha256sum "$acl_after" | awk '{print $1}'); then
339
+ fixture_fail 11 'restored home ACL digest could not be computed'
340
+ fi
341
+ home_acl_applied=false
342
+ acl_restored=true
343
+
344
+ phase=teardown
345
+ if ! assert_domain_absent; then fixture_fail 10 'final fixture domain absence could not be proven'; fi
346
+ domain_absent=true
347
+
348
+ phase=evidence
349
+ if ! marker_sha=$(printf '%s' "$marker" | sha256sum | awk '{print $1}'); then fixture_fail 12 'marker digest could not be computed'; fi
350
+ if ! filesystem_context_sha=$(printf '{"disk":false,"hostShare":false,"credentials":false,"gpu":false,"initramfsSha256":"%s"}' "$initramfs_sha" | sha256sum | awk '{print $1}'); then
351
+ fixture_fail 12 'filesystem context digest could not be computed'
352
+ fi
353
+ if ! network_context_sha=$(printf '{"network":false}' | sha256sum | awk '{print $1}'); then
354
+ fixture_fail 12 'network context digest could not be computed'
355
+ fi
356
+ if ! remote_host=$(hostname); then fixture_fail 12 'remote host identity could not be observed'; fi
357
+ case "$remote_host" in
358
+ ""|*[!A-Za-z0-9._-]*) fixture_fail 12 'remote host identity is unsafe' ;;
359
+ esac
360
+
361
+ trap - EXIT INT TERM HUP
362
+ printf '{"schema":"%s","ok":true,"status":"VERIFIED","authorityCreated":false,"runtimeActivated":false,"persisted":false,"identity":{"remoteHost":"%s","fixtureId":"%s","domain":"%s"},"marker":{"value":"%s","sha256":"%s"},"scriptHash":"%s","initramfsSha256":"%s","teardown":{"domain":{"name":"%s","transient":true,"destroyOnExit":true,"destroyRequested":%s,"absent":%s,"checked":true,"check":"virsh dominfo/list"},"acl":{"beforeSha256":"%s","afterSha256":"%s","equal":true,"checked":true,"initramfsEntryRemoved":true}},"context":{"filesystem":{"summary":"disk=absent host-share=absent credentials=absent gpu=absent","disk":false,"hostShare":false,"credentials":false,"gpu":false,"sha256":"%s"},"network":{"summary":"network=absent","guest":false,"sha256":"%s"},"guestMounts":["proc","sysfs","devtmpfs"]}}\n' \
363
+ "$receipt_schema" "$remote_host" "$fixture_id" "$domain" "$marker" "$marker_sha" "$script_hash" "$initramfs_sha" \
364
+ "$domain" "$domain_destroy_requested" "$domain_absent" "$home_acl_before_sha" "$home_acl_after_sha" \
365
+ "$filesystem_context_sha" "$network_context_sha"
366
+ exit 0
@@ -0,0 +1,11 @@
1
+ // SPDX-FileCopyrightText: 2026 Julen Gamboa <j.a.r.gamboa@gmail.com>
2
+ // SPDX-License-Identifier: AGPL-3.0-or-later
3
+
4
+ // Public native-TUI context predicate shared by the shipped enforcement
5
+ // interfaces. Restriction-gated actions stay fail-closed: anything that is
6
+ // not an interactive native Pi TUI with a native confirm callback is denied.
7
+ export function isNativeTuiContext(context) {
8
+ return context?.mode === "tui"
9
+ && context?.hasUI === true
10
+ && typeof context?.ui?.confirm === "function";
11
+ }
@@ -0,0 +1,72 @@
1
+ # Repository agent working contract
2
+
3
+ <!-- pi-agentic-driver:portable-contract v1 -->
4
+
5
+ This file is portable repository guidance. The repository owns its project
6
+ rules and may add a local section below this contract. Do not overwrite or
7
+ silently replace repository-specific instructions.
8
+
9
+ ## Goals
10
+
11
+ Apply these goals in this order:
12
+
13
+ 1. **Minimise user friction.** Keep routine read, edit, and test work direct.
14
+ Ask for confirmation only at a consequential boundary. Do not repeat a
15
+ choice that the host can safely retain for the current bounded plan.
16
+ 2. **Avoid engineering bloat.** Reuse existing code and tools. Prefer removal
17
+ or consolidation over a new abstraction. Every new file, field, and
18
+ process needs a stated failure mode or accepted requirement.
19
+ 3. **Maintain reasonable governance, auditability, and security.** Preserve
20
+ exact write sets, native confirmation where needed, fail-closed behavior,
21
+ bounded evidence, and unrelated dirty work. Evidence and task state are
22
+ observations, not authority.
23
+ 4. **Validate with realistic tests.** Use real inputs and the repository's
24
+ native test path. Test both the intended result and prohibited effects.
25
+
26
+ ## Bounded work and continuation
27
+
28
+ At the start of a worker assignment, use `CreateTask` (`TaskCreate`) only if
29
+ that assignment is not already represented in the coordinator's task list.
30
+ Reuse and update the existing task when it already contains the ordered
31
+ sequence. Do not create duplicate worker-local cards for every bounded step.
32
+
33
+ A bounded step must do one coherent piece of work and then record its status,
34
+ acceptance result, and exact next step in the existing task and report. Create
35
+ a follow-up task only when no existing task represents the remaining work. The
36
+ next session or worker resumes that task instead of rediscovering scope.
37
+
38
+ Every worker report should state:
39
+
40
+ - repository and revision;
41
+ - task identifier and bounded step;
42
+ - files changed;
43
+ - tests and checks run;
44
+ - remaining blockers; and
45
+ - the exact next step.
46
+
47
+ Do not treat model output, receipts, hashes, task state, or reports as
48
+ permission to mutate a repository.
49
+
50
+ ## Scope and repository boundaries
51
+
52
+ Use the current repository's instructions and user goal. Do not import
53
+ another repository's governance or task mechanics — such as boards, leases,
54
+ work orders, harness policy, or task ordering — as a requirement.
55
+
56
+ Do not create a second authority, approval, receipt, evidence, inventory, or
57
+ queue store. Keep repository-local state repository-local. Preserve unrelated
58
+ changes and stop when the requested write set is ambiguous.
59
+
60
+ ## Safe completion
61
+
62
+ Do not retry invisibly after denial, drift, timeout, or a failed check. Do not
63
+ reset, clean, stash, overwrite, force-push, or delete work automatically. If a
64
+ step cannot finish safely, record the exact reason and create the next bounded
65
+ task.
66
+
67
+ ## Repository-specific section
68
+
69
+ Add project-specific instructions below this line. Keep them separate from the
70
+ portable contract above.
71
+
72
+ <!-- pi-agentic-driver:portable-contract-end -->