1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# Run the gate, the cross-target checks, and the package on Linux.
#
# `check.sh` is what a person runs and this is the same three things on every
# push, plus the two nobody runs by hand: whether the arms for the platforms
# this runner is not still compile, and whether the association actually
# resolves on a machine that was not the one it was written on.
#
# What it reaches and what it does not, stated because a green tick invites more
# faith than it has earned.
#
# **The suite runs three times rather than once.** `CLAUDE.md` says why, and a
# runner is the case it was written about: the tests watch real directories
# through the platform's notifier, and event timing moves with load. Three
# rather than five only because the runner is slower than a desk, and the
# deadlines the suite waits on are seconds where the events are milliseconds.
#
# **Nothing here reaches a notification service.** The two tests that talk to
# `org.freedesktop.Notifications` are `#[ignore]`d and stay that way: there is
# no session bus on a runner, and a test that quietly passed by finding none
# would be worse than no test. What they check is measured by hand on a desktop
# and recorded in the module that holds them.
#
# **Nothing here opens a document.** `platform::testing::Recording` is what the
# suite launches through, so `xdg-open` is never called and no application is
# ever handed a payload. The whole of concept 5 step 7 is unexercised here and
# is checked by hand.
#
# Author: David M. Anderson
# Built with AI assistance (Claude, Anthropic)
name: Linux
on:
push:
pull_request:
env:
CARGO_TERM_COLOR: always
jobs:
linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: What is installed
run: rustc -V && cargo -V && dpkg-deb --version | head -1
- name: Formatting
run: cargo fmt --check
- name: Clippy
run: cargo clippy --all-targets -- -D warnings
- name: Build
run: cargo build --all-targets
# Three runs, for the reason in the header. Not `check.sh`, because that
# would run fmt and clippy again inside a step that says it is testing.
- name: Test, three times
run: for i in 1 2 3; do echo "run $i"; cargo test --quiet; done
# The arms for the platforms this runner is not. Written against
# Windows' requirements while only Linux existed, which is worth nothing if
# nobody compiles them — and when this step was first written, they did
# not compile: `identity.rs` used `windows_by_handle`, unstable since
# 2019, and the endpoint's tests imported a Unix-only function.
#
# Both are everything now. Windows was the library alone for as long as
# the binary could not be built for it at all — concept 8's named pipe was
# Phase 4 and the front door and the resident loop were Unix-only — and
# that landed, so the gate that said so comes off with it. `windows.yml`
# is what runs the suite there; this stays as the cheaper half, because a
# Linux runner says whether a change still compiles for Windows without
# anybody waiting for a Windows one.
#
# macOS is a Unix and compiles whole. It has no workflow of its own, and
# a compile is all this claims about it.
- name: The arms for the other platforms still compile
run: |
rustup target add x86_64-pc-windows-msvc x86_64-apple-darwin
cargo check --all-targets --target x86_64-pc-windows-msvc
cargo check --all-targets --target x86_64-apple-darwin
- name: Build the package
run: |
cargo install cargo-deb --locked
cargo build --release
# cargo-deb prints the path it wrote, which is the only reliable way
# to find it: `[build] target-dir` moves the target directory and no
# environment variable then says so.
cargo deb --no-build | tee /tmp/deb-path
# Two things worth reading every time. `/etc/slipcase/open.toml` must be a
# conffile or dpkg overwrites an administrator's policy on the next
# upgrade, which is the whole reason it is under `/etc`. And the binary
# must be in there at all: the asset list names it as
# `target/release/slipcase-open`, a path cargo-deb rewrites to wherever
# the target directory really is.
- name: The package holds up
run: |
deb=$(cat /tmp/deb-path)
echo "package: $deb"
dpkg-deb -c "$deb"
dpkg-deb -I "$deb" conffiles | grep -qx '/etc/slipcase/open.toml' ||
{ echo "the policy file is not a conffile" >&2; exit 1; }
dpkg-deb -c "$deb" | grep -q 'usr/bin/slipcase-open' ||
{ echo "no binary in the package" >&2; exit 1; }
dpkg-deb -f "$deb" Depends | grep -q slipcase-common ||
{ echo "the package does not depend on slipcase-common" >&2; exit 1; }
# The one layer no test can reach. `/etc/slipcase/open.toml` is
# deliberately not redirectable — a machine policy an environment
# variable could move is not one — so the suite has no way to put a file
# there, and the machine arm of `policy` is unexercised by everything
# above. Here there is a machine to write to.
#
# What it checks is the unit test `the_policy_file_the_package_ships_says
# _nothing` makes of the shipped file, asked through the real resolution
# instead of a `Files` pointed at the repository copy: an untouched
# install must not read as administered. A stray uncommented line in that
# file is a policy nobody wrote, enforced on every machine that installs
# the package, and announced to each of them as their settings being
# administered.
- name: The shipped policy file reads as documentation
run: |
sudo install -D -m 0644 packaging/linux/open.toml /etc/slipcase/open.toml
said=$(cargo run --release --quiet -- policy)
printf '%s\n' "$said"
printf '%s\n' "$said" | grep -q '/etc/slipcase/open.toml (there, and sets nothing)' ||
{ echo "the shipped file is not being read, or is setting something" >&2; exit 1; }
# `if` rather than `grep ... && { exit 1; }`: under `set -e` a whole
# AND-list that fails is a failed command, so the good case — no
# match — would end the step with the inverted verdict.
if printf '%s\n' "$said" | grep -qi 'administered'; then
echo "an untouched install reads as administered" >&2
exit 1
fi
sudo rm -rf /etc/slipcase
- name: What lintian says
run: |
sudo apt-get update -qq
sudo apt-get install -y -qq lintian
lintian --info --tag-display-limit 0 --fail-on error,warning "$(cat /tmp/deb-path)"
# The only step that asks the platform anything. Concept 4 wants this
# tool's entry in the Open With list, and that is two questions with no
# eyes in them.
#
# `gio` rather than `xdg-mime`: it asks GLib for the content type, which
# is the path a GTK file manager takes, and it needs no session. The
# sibling repository's workflow found that `xdg-mime` without a session
# falls back to `file`, which reads magic bytes — and SPEC §4 reserves
# none, so it would answer `application/zip` however well the glob was
# installed.
#
# `slipcase-common` declares the type and this package does not, so it is
# installed first or there is nothing for the entry to resolve against.
# Pinned, because this clones another repository and runs a shell script
# out of it: whoever can land a commit there executes code here. Bump it
# deliberately, and take the hash from `git rev-parse` rather than typing
# it — the first version of this line was a real short hash with the rest
# invented, which clones fine and fails the checkout with exit 128.
#
# COMMON-PIN: 2d20438f1216c45b2b931ba40c0c2997f7b07671
- name: The type and the entry are ours
run: |
sudo apt-get install -y -qq shared-mime-info desktop-file-utils libglib2.0-bin
git clone --filter=blob:none --no-checkout \
https://github.com/excelano/slipcase-common.git "${RUNNER_TEMP}/common"
git -C "${RUNNER_TEMP}/common" checkout --detach \
2d20438f1216c45b2b931ba40c0c2997f7b07671
"${RUNNER_TEMP}/common/install.sh"
./packaging/linux/install.sh
# A real container, because an empty file answers
# `application/x-zerosize` whatever the glob says. Built here rather
# than committed: a fixture in the tree would be one more thing that
# can drift from what SPEC §2 requires.
python3 -c 'import zipfile,sys; z=zipfile.ZipFile(sys.argv[1],"w"); z.writestr("slipcase.metadata.toml","slipcase_version = \"1.0\"\n\n[payload]\nfile = \"report.txt\"\n"); z.writestr("report.txt","a report\n"); z.close()' "${RUNNER_TEMP}/sample.slpc"
type=$(gio info -a standard::content-type "${RUNNER_TEMP}/sample.slpc" |
sed -n 's/.*standard::content-type: //p')
echo "content type: ${type}"
[ "$type" = "application/x.slipcase+zip" ] ||
{ echo "the type is not ours" >&2; exit 1; }
gio mime application/x.slipcase+zip | grep -q 'slipcase-open.desktop' ||
{ echo "this tool is not registered against the type" >&2; exit 1; }
gio mime application/x.slipcase+zip