@microck/raft 0.6.0 → 0.7.0

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.
Files changed (59) hide show
  1. package/README.md +13 -4
  2. package/deploy/build-image.py +4 -2
  3. package/deploy/verify-controls.py +5 -5
  4. package/deploy/verify-features.py +35 -2
  5. package/deploy/verify-jobs.py +264 -0
  6. package/deploy/verify-limits.py +20 -2
  7. package/deploy/verify-live.py +12 -3
  8. package/deploy/verify-output.py +208 -0
  9. package/deploy/verify-release.py +33 -4
  10. package/deploy/verify-run.py +571 -0
  11. package/deploy/verify-self-update.py +9 -1
  12. package/deploy/verify-sync.py +122 -0
  13. package/docs/benchmarks/read-latency-2026-10-29.json +70 -0
  14. package/docs/boat-capabilities.md +60 -7
  15. package/docs/boat-parity-design.md +1 -0
  16. package/docs/boot-performance.mdx +21 -0
  17. package/docs/contract.md +76 -2
  18. package/docs/first-box.mdx +22 -19
  19. package/docs/guides/background-jobs.mdx +38 -31
  20. package/docs/guides/backup-recovery.mdx +3 -2
  21. package/docs/guides/ci-jobs.mdx +209 -0
  22. package/docs/guides/files.mdx +2 -2
  23. package/docs/guides/index.mdx +31 -0
  24. package/docs/guides/long-running-services.mdx +1 -1
  25. package/docs/guides/meta.json +7 -2
  26. package/docs/guides/project-setup.mdx +1 -1
  27. package/docs/guides/scripting-ci.mdx +28 -9
  28. package/docs/guides/snapshots-forks.mdx +3 -1
  29. package/docs/guides/upgrades.mdx +7 -7
  30. package/docs/index.mdx +26 -28
  31. package/docs/maintainers/index.mdx +18 -0
  32. package/docs/maintainers/meta.json +12 -0
  33. package/docs/meta.json +5 -9
  34. package/docs/operations.mdx +7 -0
  35. package/docs/publishing.md +14 -4
  36. package/docs/reference/capacity.mdx +2 -1
  37. package/docs/reference/cli.md +85 -19
  38. package/docs/reference/meta.json +3 -1
  39. package/docs/releases/0.2.0.md +1 -0
  40. package/docs/releases/0.2.1.md +1 -0
  41. package/docs/releases/0.3.0.md +1 -0
  42. package/docs/releases/0.4.0.md +1 -0
  43. package/docs/releases/0.5.0.md +1 -0
  44. package/docs/releases/0.6.0.md +1 -0
  45. package/docs/releases/0.7.0.md +23 -0
  46. package/docs/releases/index.mdx +21 -0
  47. package/docs/releases/meta.json +15 -0
  48. package/docs/requirements.mdx +1 -1
  49. package/docs/reviews/docs-audit-2026-10-24.md +92 -0
  50. package/docs/reviews/performance-audit-2026-10-29.md +90 -0
  51. package/docs/site-development.md +1 -0
  52. package/docs/verification.mdx +21 -3
  53. package/package.json +3 -1
  54. package/raft.py +380 -179
  55. package/raft_files.py +13 -1
  56. package/raft_jobs.py +390 -0
  57. package/raft_output.py +166 -0
  58. package/raft_run.py +330 -0
  59. package/skills/raft-cli/SKILL.md +78 -197
@@ -0,0 +1,208 @@
1
+ """Verify JSONL bytes, errors, completion discovery and native command status."""
2
+
3
+ import argparse
4
+ import base64
5
+ import importlib.util
6
+ import json
7
+ import os
8
+ from pathlib import Path
9
+ import subprocess
10
+ import sys
11
+ import tempfile
12
+
13
+ ROOT = Path(__file__).resolve().parent.parent
14
+ sys.path.insert(0, str(ROOT))
15
+ import raft # noqa: E402
16
+ import raft_output # noqa: E402
17
+
18
+
19
+ def events(command, env):
20
+ response = subprocess.run(command, env=env, capture_output=True)
21
+ records = [json.loads(line) for line in response.stdout.splitlines()]
22
+ assert not response.stderr, response.stderr
23
+ assert records[-1] == {"type": "exit", "code": response.returncode}, records
24
+ return response, records
25
+
26
+
27
+ def stream(records, kind):
28
+ return b"".join(base64.b64decode(r["data"]) for r in records if r["type"] == kind)
29
+
30
+
31
+ def verify():
32
+ with tempfile.TemporaryDirectory(prefix="raft-output-") as directory:
33
+ env = {**os.environ, "HOME": directory, "RAFT_NO_UPDATE_CHECK": "1"}
34
+ command = [sys.executable, str(ROOT / "raft.py"), "--json"]
35
+ response, records = events(command + ["--help"], env)
36
+ assert response.returncode == 0 and b"usage:" in stream(records, "stdout")
37
+ for arguments in [["invalid"], ["new", "--ttl", "no"], ["new", "--unknown"]]:
38
+ response, records = events(command + arguments, env)
39
+ assert response.returncode == 2 and records[0]["code"] == "usage", records
40
+ response, records = events(command + ["locations"], env)
41
+ assert response.returncode == 1 and records[0]["code"] == "operation", records
42
+ response, records = events(command + ["run", "--", "true"], env)
43
+ failures = [record for record in records if record["type"] == "error"]
44
+ assert response.returncode == 1 and len(failures) == 1, records
45
+ assert failures[0]["code"] == "operation" and "incus.json" in failures[0]["message"]
46
+ assert b"raft run: finished" in stream(records, "stderr"), records
47
+ plain = subprocess.run(
48
+ [sys.executable, str(ROOT / "raft.py"), "run", "--", "true"],
49
+ env=env,
50
+ capture_output=True,
51
+ )
52
+ assert plain.returncode == 1 and not plain.stdout
53
+ assert plain.stderr.startswith(b"raft run: ") and b"incus.json" in plain.stderr
54
+ inventory = Path(directory) / ".config/raft"
55
+ inventory.mkdir(parents=True)
56
+ (inventory / "incus.json").write_text('{"fixture":{"ssh":"offline.invalid","image":""}}')
57
+ response, records = events(command + ["locations"], env)
58
+ assert records[0] == {
59
+ "type": "data",
60
+ "value": {"location": "fixture", "default": True, "note": ""},
61
+ }
62
+ # Real subprocess descriptors, including binary bytes and more than a pipe
63
+ # buffer on both channels. Reconstruction must be exact and cannot hang.
64
+ program = """
65
+ import os, subprocess, sys
66
+ import raft_output
67
+ out = raft_output.JsonOutput().start()
68
+ subprocess.run([sys.executable, '-c', 'import os; os.write(1, bytes(range(256))*512); os.write(2,b"error"*32768)'], check=True)
69
+ out.finish(7)
70
+ sys.exit(7)
71
+ """
72
+ response, records = events(
73
+ [sys.executable, "-c", program], {**env, "PYTHONPATH": str(ROOT)}
74
+ )
75
+ assert response.returncode == 7
76
+ assert stream(records, "stdout") == bytes(range(256)) * 512
77
+ assert stream(records, "stderr") == b"error" * 32768
78
+ for number in (9, 15):
79
+ # The event and shell status must agree after real child termination.
80
+ signalled = f"""
81
+ import subprocess, sys
82
+ import raft, raft_output
83
+ out = raft_output.JsonOutput().start()
84
+ child = subprocess.run([sys.executable, '-c', 'import os, signal; os.kill(os.getpid(), {number})'])
85
+ status = raft.exit_status(child.returncode)
86
+ out.finish(status)
87
+ sys.exit(status)
88
+ """
89
+ response, records = events(
90
+ [sys.executable, "-c", signalled], {**env, "PYTHONPATH": str(ROOT)}
91
+ )
92
+ assert response.returncode == 128 + number, records
93
+ parser = raft.parser()
94
+ parser.add_argument("--future-flag", action="store_true")
95
+ assert raft_output.candidates(parser, ["--future"]) == ["--future-flag"]
96
+ assert "--location" in raft_output.candidates(parser, ["new", "--lo"])
97
+ assert "set" in raft_output.candidates(parser, ["config", ""])
98
+ assert raft_output.candidates(parser, ["exec", "box", "--", ""]) == []
99
+ assert raft_output.candidates(parser, ["completions", "z"]) == ["zsh"]
100
+ for shell in ("bash", "zsh", "fish"):
101
+ script = subprocess.run(
102
+ [sys.executable, str(ROOT / "raft.py"), "completions", shell],
103
+ env=env,
104
+ capture_output=True,
105
+ check=True,
106
+ )
107
+ assert b"_complete" in script.stdout and not script.stderr
108
+ setup = raft_output.completions("bash")
109
+ completed = subprocess.run(
110
+ [
111
+ "bash",
112
+ "-c",
113
+ setup
114
+ + "\nraft() { "
115
+ + sys.executable
116
+ + " "
117
+ + str(ROOT / "raft.py")
118
+ + ' "$@"; }\nCOMP_WORDS=(raft config ""); COMP_CWORD=2; _raft_complete; printf "%s\\n" "${COMPREPLY[@]}"',
119
+ ],
120
+ env=env,
121
+ capture_output=True,
122
+ check=True,
123
+ )
124
+ assert "set" in completed.stdout.decode().splitlines(), completed
125
+ print("JSONL bytes, subprocess streams, structured errors, reports and Bash completion passed")
126
+
127
+
128
+ def native(location):
129
+ spec = importlib.util.spec_from_file_location("live", ROOT / "deploy/verify-live.py")
130
+ live = importlib.util.module_from_spec(spec)
131
+ spec.loader.exec_module(live)
132
+ box = live.raft(
133
+ "new",
134
+ "--location",
135
+ location,
136
+ "--ttl",
137
+ "600",
138
+ "--disposable",
139
+ "--purpose",
140
+ "JSON output verification",
141
+ "--owner",
142
+ "verify-output",
143
+ ).stdout.strip()
144
+ try:
145
+ live.wait_running(box)
146
+ response, records = events(
147
+ [
148
+ sys.executable,
149
+ str(ROOT / "raft.py"),
150
+ "--json",
151
+ "exec",
152
+ box,
153
+ "--",
154
+ "sh",
155
+ "-c",
156
+ "printf hello; printf error >&2; exit 7",
157
+ ],
158
+ live.CONTROLLER_ENV,
159
+ )
160
+ assert response.returncode == 7
161
+ assert stream(records, "stdout") == b"hello" and stream(records, "stderr") == b"error", (
162
+ records
163
+ )
164
+ response, records = events(
165
+ [sys.executable, str(ROOT / "raft.py"), "--json", "info", box], live.CONTROLLER_ENV
166
+ )
167
+ assert records[0]["value"]["name"] == box.partition(":")[2], records
168
+ with tempfile.TemporaryDirectory(prefix="raft-output-artifacts-") as artifacts:
169
+ response, records = events(
170
+ [
171
+ sys.executable,
172
+ str(ROOT / "raft.py"),
173
+ "--json",
174
+ "run",
175
+ "--location",
176
+ location,
177
+ "--purpose",
178
+ "JSON artifact failure verification",
179
+ "--owner",
180
+ "verify-output",
181
+ "--pull",
182
+ "/missing-raft-artifacts",
183
+ str(Path(artifacts) / "collected"),
184
+ "--",
185
+ "true",
186
+ ],
187
+ live.CONTROLLER_ENV,
188
+ )
189
+ assert response.returncode == 1, records
190
+ assert any(
191
+ record["type"] == "error"
192
+ and record["code"] == "operation"
193
+ and "could not collect /missing-raft-artifacts" in record["message"]
194
+ for record in records
195
+ ), records
196
+ assert b"cleanup confirmed" in stream(records, "stderr"), records
197
+ print(f"{location}: JSON exec, reports, run artifact failure and cleanup passed")
198
+ finally:
199
+ live.raft("destroy", box)
200
+
201
+
202
+ if __name__ == "__main__":
203
+ cli = argparse.ArgumentParser(description=__doc__)
204
+ cli.add_argument("--location")
205
+ args = cli.parse_args()
206
+ verify()
207
+ if args.location:
208
+ native(args.location)
@@ -68,12 +68,21 @@ def verify_npm():
68
68
  content = archive.extractfile(member).read()
69
69
  inspect(member.name, content)
70
70
  names.add(member.name)
71
- if member.name in {"package/raft.py", "package/raft_files.py"}:
71
+ if member.name in {
72
+ "package/raft.py",
73
+ "package/raft_files.py",
74
+ "package/raft_run.py",
75
+ "package/raft_jobs.py",
76
+ "package/raft_output.py",
77
+ }:
72
78
  if content != (ROOT / PurePosixPath(member.name).name).read_bytes():
73
79
  raise ValueError("npm runtime differs from canonical Python source")
74
80
  required = {
75
81
  "package/raft.py",
76
82
  "package/raft_files.py",
83
+ "package/raft_run.py",
84
+ "package/raft_jobs.py",
85
+ "package/raft_output.py",
77
86
  "package/LICENSE",
78
87
  "package/deploy/deploy.py",
79
88
  "package/images/incus/build.sh",
@@ -105,12 +114,28 @@ def main():
105
114
  raise ValueError("Unexpected non-file member: " + member.name)
106
115
  inspect(member.name, archive.extractfile(member).read())
107
116
  names = {PurePosixPath(member.name).parts[1] for member in members}
108
- if not {"raft.py", "deploy", "docs", "images", "skills", "LICENSE"} <= names:
117
+ if (
118
+ not {
119
+ "raft.py",
120
+ "raft_files.py",
121
+ "raft_run.py",
122
+ "raft_jobs.py",
123
+ "raft_output.py",
124
+ "deploy",
125
+ "docs",
126
+ "images",
127
+ "skills",
128
+ "LICENSE",
129
+ }
130
+ <= names
131
+ ):
109
132
  raise ValueError("Source archive missing installation resources")
110
133
  with zipfile.ZipFile(wheels[0]) as archive:
111
134
  for name in archive.namelist():
112
135
  inspect(name, archive.read(name))
113
- if not {"raft.py", "raft_files.py"} <= set(archive.namelist()):
136
+ if not {"raft.py", "raft_files.py", "raft_run.py", "raft_jobs.py", "raft_output.py"} <= set(
137
+ archive.namelist()
138
+ ):
114
139
  raise ValueError("Wheel missing CLI module")
115
140
  # A new environment avoids both editable checkouts and uv's cached same-version
116
141
  # tool environments. Run the installed entry point outside the source tree.
@@ -123,7 +148,11 @@ def main():
123
148
  check=True,
124
149
  )
125
150
  verify_cli(environment / "bin/raft", work)
126
- subprocess.run([str(python), "-I", "-c", "import raft, raft_files"], cwd=work, check=True)
151
+ subprocess.run(
152
+ [str(python), "-I", "-c", "import raft, raft_files, raft_run, raft_jobs, raft_output"],
153
+ cwd=work,
154
+ check=True,
155
+ )
127
156
  verify_npm()
128
157
  print("Release archives and isolated wheel installation passed")
129
158