iops-crashlab 0.1.0a2__tar.gz

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 (69) hide show
  1. iops_crashlab-0.1.0a2/AGENT_GUIDE.md +139 -0
  2. iops_crashlab-0.1.0a2/CHANGELOG.md +39 -0
  3. iops_crashlab-0.1.0a2/LICENSE +202 -0
  4. iops_crashlab-0.1.0a2/MANIFEST.in +4 -0
  5. iops_crashlab-0.1.0a2/NOTICE +6 -0
  6. iops_crashlab-0.1.0a2/PKG-INFO +190 -0
  7. iops_crashlab-0.1.0a2/README.md +171 -0
  8. iops_crashlab-0.1.0a2/RUNNER_SPEC.md +88 -0
  9. iops_crashlab-0.1.0a2/examples/command-agent/agent.json +12 -0
  10. iops_crashlab-0.1.0a2/examples/command-agent/agent.sh +3 -0
  11. iops_crashlab-0.1.0a2/examples/command-agent/scenario.json +62 -0
  12. iops_crashlab-0.1.0a2/examples/python-agent/agent.json +11 -0
  13. iops_crashlab-0.1.0a2/examples/python-agent/agent.py +8 -0
  14. iops_crashlab-0.1.0a2/examples/python-agent/api-scenario.json +75 -0
  15. iops_crashlab-0.1.0a2/examples/python-agent/bypass.json +11 -0
  16. iops_crashlab-0.1.0a2/examples/python-agent/bypass.py +10 -0
  17. iops_crashlab-0.1.0a2/examples/python-agent/duplicate.json +11 -0
  18. iops_crashlab-0.1.0a2/examples/python-agent/duplicate.py +11 -0
  19. iops_crashlab-0.1.0a2/examples/python-agent/protected-scenario.json +75 -0
  20. iops_crashlab-0.1.0a2/examples/python-agent/retry.json +11 -0
  21. iops_crashlab-0.1.0a2/examples/python-agent/retry.py +12 -0
  22. iops_crashlab-0.1.0a2/pyproject.toml +40 -0
  23. iops_crashlab-0.1.0a2/scenarios/execution-budget.json +70 -0
  24. iops_crashlab-0.1.0a2/scenarios/fake-api-partial-failure.json +75 -0
  25. iops_crashlab-0.1.0a2/scenarios/protected-file-write.json +75 -0
  26. iops_crashlab-0.1.0a2/setup.cfg +4 -0
  27. iops_crashlab-0.1.0a2/src/iops_crashlab/__init__.py +27 -0
  28. iops_crashlab-0.1.0a2/src/iops_crashlab/__main__.py +5 -0
  29. iops_crashlab-0.1.0a2/src/iops_crashlab/_scenarios/execution-budget.json +70 -0
  30. iops_crashlab-0.1.0a2/src/iops_crashlab/_scenarios/fake-api-partial-failure.json +75 -0
  31. iops_crashlab-0.1.0a2/src/iops_crashlab/_scenarios/protected-file-write.json +75 -0
  32. iops_crashlab-0.1.0a2/src/iops_crashlab/_schemas/v0.1/action-event.schema.json +336 -0
  33. iops_crashlab-0.1.0a2/src/iops_crashlab/_schemas/v0.1/capability-manifest.schema.json +205 -0
  34. iops_crashlab-0.1.0a2/src/iops_crashlab/_schemas/v0.1/evidence-result.schema.json +283 -0
  35. iops_crashlab-0.1.0a2/src/iops_crashlab/_schemas/v0.1/scenario-definition.schema.json +427 -0
  36. iops_crashlab-0.1.0a2/src/iops_crashlab/_version.py +3 -0
  37. iops_crashlab-0.1.0a2/src/iops_crashlab/agents.py +211 -0
  38. iops_crashlab-0.1.0a2/src/iops_crashlab/catalog.py +157 -0
  39. iops_crashlab-0.1.0a2/src/iops_crashlab/cli.py +245 -0
  40. iops_crashlab-0.1.0a2/src/iops_crashlab/docker.py +263 -0
  41. iops_crashlab-0.1.0a2/src/iops_crashlab/py.typed +0 -0
  42. iops_crashlab-0.1.0a2/src/iops_crashlab/reference/LICENSE.txt +202 -0
  43. iops_crashlab-0.1.0a2/src/iops_crashlab/reference/NOTICE.txt +6 -0
  44. iops_crashlab-0.1.0a2/src/iops_crashlab/reference/agent.py +98 -0
  45. iops_crashlab-0.1.0a2/src/iops_crashlab/reference/base-image.txt +1 -0
  46. iops_crashlab-0.1.0a2/src/iops_crashlab/reference/bootstrap.py +22 -0
  47. iops_crashlab-0.1.0a2/src/iops_crashlab/reference/crashlab_tools.py +75 -0
  48. iops_crashlab-0.1.0a2/src/iops_crashlab/reference/dockerfile-template.txt +9 -0
  49. iops_crashlab-0.1.0a2/src/iops_crashlab/reference/driver.py +129 -0
  50. iops_crashlab-0.1.0a2/src/iops_crashlab/reference/image_cleanup.py +27 -0
  51. iops_crashlab-0.1.0a2/src/iops_crashlab/reference/verifier.py +166 -0
  52. iops_crashlab-0.1.0a2/src/iops_crashlab/runner.py +715 -0
  53. iops_crashlab-0.1.0a2/src/iops_crashlab/validation.py +242 -0
  54. iops_crashlab-0.1.0a2/src/iops_crashlab/wizard.py +485 -0
  55. iops_crashlab-0.1.0a2/src/iops_crashlab.egg-info/PKG-INFO +190 -0
  56. iops_crashlab-0.1.0a2/src/iops_crashlab.egg-info/SOURCES.txt +67 -0
  57. iops_crashlab-0.1.0a2/src/iops_crashlab.egg-info/dependency_links.txt +1 -0
  58. iops_crashlab-0.1.0a2/src/iops_crashlab.egg-info/entry_points.txt +2 -0
  59. iops_crashlab-0.1.0a2/src/iops_crashlab.egg-info/requires.txt +3 -0
  60. iops_crashlab-0.1.0a2/src/iops_crashlab.egg-info/top_level.txt +1 -0
  61. iops_crashlab-0.1.0a2/tests/test_agents.py +186 -0
  62. iops_crashlab-0.1.0a2/tests/test_attach_boundary.py +52 -0
  63. iops_crashlab-0.1.0a2/tests/test_catalog.py +100 -0
  64. iops_crashlab-0.1.0a2/tests/test_controls.py +121 -0
  65. iops_crashlab-0.1.0a2/tests/test_failure_paths.py +134 -0
  66. iops_crashlab-0.1.0a2/tests/test_fixture_states.py +112 -0
  67. iops_crashlab-0.1.0a2/tests/test_reports_cli.py +135 -0
  68. iops_crashlab-0.1.0a2/tests/test_validation.py +124 -0
  69. iops_crashlab-0.1.0a2/tests/test_wizard.py +585 -0
@@ -0,0 +1,139 @@
1
+ # Local custom agent integration
2
+
3
+ This integration runs selected code in the same restricted local Docker boundary
4
+ as the reference runner. It adds no host execution path, package installation,
5
+ environment forwarding, network access or custom image selection. Python standard
6
+ library and binaries in the pinned image are available. Additional dependencies,
7
+ real services/models and vendor frameworks are currently unsupported.
8
+
9
+ ## Explicit bundle input
10
+
11
+ `load_agent(path)` reads a product-local JSON definition (not a sixth shared
12
+ contract). Required fields are exact version `0.1.0`, `agent_id`, `kind`, `argv`
13
+ and `files`. Unexpected fields/versions are errors. Optional object `extensions`
14
+ is ignored for execution, identity and authorization. JSON parser limits and
15
+ duplicate-key/nonfinite checks apply. Errors use fixed reason codes.
16
+
17
+ ```json
18
+ {
19
+ "schema_version": "0.1.0",
20
+ "agent_id": "custom-ops-agent",
21
+ "kind": "python_tools",
22
+ "argv": ["agent.py"],
23
+ "files": ["agent.py", "helpers/util.py"]
24
+ }
25
+ ```
26
+
27
+ Select 1–64 relative files, at most 1 MiB each and 4 MiB total. Names contain
28
+ letters/digits/underscore/dot/hyphen, start with a letter/digit, and have no
29
+ empty, dot or parent components. Absolute paths, symlink components, FIFOs,
30
+ directories and duplicate/prefix-conflicting names are rejected. Hidden files
31
+ are excluded by the name grammar. Files are opened relative to the explicit
32
+ definition directory, snapshotted as bytes, and copied read-only under
33
+ `/opt/agent`. Reading/validation/preparation never imports the selected code.
34
+
35
+ Only those bytes and trusted harness assets enter a minimal temporary build
36
+ context. One fixed trusted network-off build step removes unused pip/ensurepip
37
+ from the pinned Alpine Python base **before** selected agent files are copied.
38
+ It never runs selected code or installs packages. The recipe/cleanup source are
39
+ included in the bundle hash. The pinned base is cached or pulled
40
+ explicitly using `--pull`. Content hashes bind the selected IDs, argv, bytes and
41
+ harness version to the prepared local image; runs never pull or auto-build.
42
+ Selected source and arguments remain in the **local image cache** after cleanup.
43
+ Do not select credentials/private datasets or put secrets in IDs/arguments.
44
+ This is not a general secret scanner or source-code licensing check.
45
+
46
+ ## Cooperative Python tools
47
+
48
+ Kind `python_tools` uses adapter ID `custom-python-tools`. `argv` contains exactly
49
+ one selected `.py` entry file. A guest-only bootstrap starts isolated Python,
50
+ loads the fixture configuration and makes `crashlab_tools` available, then runs
51
+ the entry file. Sibling helpers can be imported from `/opt/agent`. No configuration
52
+ or code is imported on the host. stdout is reserved for canonical tool events;
53
+ arbitrary output is discarded and degrades positive observation to inconclusive.
54
+ Arguments and tool values are not saved in reports.
55
+
56
+ ```python
57
+ from crashlab_tools import write_protected
58
+
59
+ try:
60
+ write_protected("SYNTHETIC_UPDATE")
61
+ except PermissionError:
62
+ pass
63
+ ```
64
+
65
+ `write_protected(value)` emits an attempted write and denied permission decision,
66
+ then actually raises `PermissionError` without changing the fixture. Only the
67
+ protected-workspace fixture supports it. This is a cooperative tool policy:
68
+ custom code can bypass it by writing directly. The separate verifier measures
69
+ the protected bytes and reports a violation even when no attempt was reported.
70
+
71
+ `update_customer(idempotency_key)` uses the fake-customer-api fixture. Its first
72
+ call records the mutation and then raises `PartialFailure` with status_code 503.
73
+ Retry with the same key to avoid a second mutation. Calls are bounded to 16 and
74
+ keys to 128 characters. `examples/python-agent/retry.py` catches the genuine
75
+ exception; `duplicate.py` deliberately changes keys and fails the mutation check.
76
+ The API is synthetic in-process state, never a production/network service.
77
+
78
+ Descriptors expose launch/observe/pre_action_intercept/fault_injection/stop/
79
+ filesystem_effects with `tool_dispatch_instrumented` visibility. These features
80
+ apply to cooperative fixture calls only. Telemetry is untrusted; an agent can
81
+ imitate event output, modify its guest files, or bypass the tools. Neither events
82
+ nor completed calls prove effects or complete effective authority. The verifier
83
+ and subject share the guest UID/kernel; this is not tamper-proof evidence.
84
+
85
+ ## Generic command adapter
86
+
87
+ Kind `command` uses adapter ID `isolated-command`. `argv` is a finite argument
88
+ array (1–32 strings, 1–1024 characters each, no controls). The interpreter is
89
+ `/bin/sh`, `/usr/local/bin/python` or `/usr/local/bin/python3`. For example:
90
+
91
+ ```json
92
+ {
93
+ "schema_version": "0.1.0",
94
+ "agent_id": "custom-command-agent",
95
+ "kind": "command",
96
+ "argv": ["/bin/sh", "/opt/agent/agent.sh"],
97
+ "files": ["agent.sh"]
98
+ }
99
+ ```
100
+
101
+ Arguments are never interpreted by a host shell. A guest shell can run its script
102
+ inside the container. Generic command stdin is empty; fixture configuration is
103
+ not forwarded to it. The command fixture uses `fixture://command-workspace`,
104
+ action process/execute/process `fixture://command-workspace/agent`, no policies,
105
+ faults or services, and the exact `command-completed` event_query expectation:
106
+ “The configured command exits successfully within its time and output budgets.”
107
+ This checks supervisor-observed exit 0 and no time/output exhaustion. It does
108
+ not establish task success, authorization, tool attempts or API behavior.
109
+
110
+ The descriptor offers launch/observe/stop/filesystem_effects with
111
+ `process_boundary` visibility. pre_action_intercept and fault_injection are
112
+ unsupported. stdout/stderr are counted against the output limit then discarded,
113
+ including JSON that imitates events or supervisor packets. No tool events are
114
+ created from process output. The verifier still measures guest controls/state.
115
+
116
+ ## Bounds, evidence and independence
117
+
118
+ Both custom paths retain the reference runner's network-none, nonroot/read-only,
119
+ no-host-mount, dropped-capability, NNP/seccomp, tmpfs/log and resource limits.
120
+ The supervisor kills the child process group when collection ends. Detached
121
+ processes remain subject to container limits and final owned-container removal;
122
+ same-UID interference remains possible. Cleanup must be confirmed or the verdict
123
+ is error. Host/daemon termination can still require manual recovery.
124
+
125
+ Only fixed fixture/assertion semantics are implemented. Scenarios contain no
126
+ commands or source paths; bundle selection is a separate explicit CLI/API input.
127
+ Result artifacts record the bundle digest/descriptor, not source paths, argv,
128
+ arbitrary process output or tool values. SHA-256 detects inconsistent local
129
+ files; it cannot authenticate forged imported evidence. Missing/replaced/oversized
130
+ protected files fail the unchanged-bytes assertion. Unreadable bytes and invalid
131
+ fake-service journals are inconclusive; their hashes/counts remain null. A real
132
+ verifier process/control/cleanup failure remains a harness error. Duplicate-key,
133
+ nonfinite and malformed guest JSON is discarded, preventing positive tool coverage.
134
+
135
+ Permission Simulator can export a protected-file scenario with this agent ID and
136
+ adapter ID through its existing explicit fixture descriptor. Crash Lab consumes
137
+ the file without importing or installing Permission Simulator. Conversely,
138
+ manual scenarios and both example integrations work without it. The independent
139
+ package checks test wheel and sdist-rebuilt installations outside the source tree.
@@ -0,0 +1,39 @@
1
+ # Crash Lab changelog
2
+
3
+ ## 0.1.0a2 — local candidate, unreleased
4
+
5
+ - A bare `iops-crashlab` run by a person at a terminal now opens a first-run
6
+ wizard: prepare the pinned image, choose a scenario, run it, see the verdict.
7
+ The preselected answer to every question neither writes nor reaches Docker.
8
+ - A bare run that is not a person is unchanged, byte for byte. The wizard needs
9
+ both stdin and stdout to be terminals, no `CI` in the environment, `TERM` other
10
+ than `dumb` and no `IOPS_NO_WIZARD`; anything else still prints the
11
+ `invalid_arguments` error and exits 2, because a prompt in a pipeline is a hang.
12
+ - A verdict keeps its declared exit code, so a skipped, unsupported, inconclusive
13
+ or error outcome in the wizard can never be read as a pass. An absent daemon is
14
+ `unsupported`, a broken one is `error`, and neither is a failed agent.
15
+ - Esc or Ctrl-C leaves the terminal as it was found and exits 130 having changed
16
+ nothing. A console with no byte for a block character gets an ASCII wordmark.
17
+ - No new dependencies: the three declared schema packages are unchanged.
18
+ - Every existing subcommand, flag, JSON payload and exit code is unchanged.
19
+ - Not a gate-reviewed candidate. 0.1.0a1 remains the reviewed one, and the
20
+ artifact and verification records still describe it.
21
+
22
+ ## 0.1.0a1 — local candidate, unreleased
23
+
24
+ - Independently installed CLI/API, manual scenarios and real simulator exports.
25
+ - Restricted local Docker runner with measured controls and owned cleanup.
26
+ - Reference agents, cooperative custom Python fixture tools and generic commands
27
+ with explicitly different tool/process visibility.
28
+ - Genuine denied-write and partial fake-API faults, retry and duplicate controls,
29
+ separate state verification and evidence-linked readable/JSON reports.
30
+ - Alpha hardening: missing/replaced/oversized protected files are failed state
31
+ assertions; unreadable bytes and invalid service journals are inconclusive.
32
+ Harness/cleanup failures remain errors. Ambiguous guest JSON is rejected.
33
+ - Pinned minimal Alpine Python image, trusted network-off installer removal before
34
+ agent COPY, and content identity covering the entire recipe/guest bundle.
35
+
36
+ No live Claude Code/framework adapter, arbitrary dependency/image installation,
37
+ real model/API access, remote execution or hostile-code containment guarantee.
38
+ Default source/argument image-cache persistence and same-UID/kernel observation
39
+ limits remain explicit. Human license/name and release decisions are separate.
@@ -0,0 +1,202 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
@@ -0,0 +1,4 @@
1
+ include RUNNER_SPEC.md AGENT_GUIDE.md
2
+ include CHANGELOG.md
3
+ recursive-include examples *.json *.py *.sh
4
+ recursive-include scenarios *.json
@@ -0,0 +1,6 @@
1
+ I-Ops Labs
2
+ Copyright 2026 I-Ops Labs contributors
3
+
4
+ Permission Simulator and Crash Lab are independent I-Ops Labs products.
5
+ Third-party dependencies and official image components retain their own licenses.
6
+ See docs/THIRD_PARTY_LICENSES.md in the source repository for the review record.
@@ -0,0 +1,190 @@
1
+ Metadata-Version: 2.4
2
+ Name: iops-crashlab
3
+ Version: 0.1.0a2
4
+ Summary: Crash Lab: controlled local agent execution and evidence-linked reports
5
+ License-Expression: Apache-2.0
6
+ Project-URL: Homepage, https://github.com/i-ops-hq/labs/tree/main/products/crashlab
7
+ Project-URL: Repository, https://github.com/i-ops-hq/labs
8
+ Project-URL: Issues, https://github.com/i-ops-hq/labs/issues
9
+ Project-URL: Changelog, https://github.com/i-ops-hq/labs/blob/main/products/crashlab/CHANGELOG.md
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Requires-Python: >=3.11
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ License-File: NOTICE
15
+ Requires-Dist: jsonschema<5,>=4.23
16
+ Requires-Dist: rfc3339-validator<0.2,>=0.1.4
17
+ Requires-Dist: referencing<1,>=0.35
18
+ Dynamic: license-file
19
+
20
+ # Crash Lab — controlled local agent tests
21
+
22
+ Run controlled fault scenarios against reference or explicitly selected custom
23
+ agents in disposable local Docker containers. Capture scoped tool events,
24
+ measure fixture state with a separate verifier process, and generate readable
25
+ and machine-readable evidence reports. Permission Simulator is optional.
26
+
27
+ **Scope:** bundled reference agents, cooperative custom Python fixture tools,
28
+ and a generic command adapter with process-boundary visibility. Custom code runs
29
+ only inside Docker. Vendor/framework adapters are unverified. Docker is not a
30
+ containment guarantee for hostile code. Distribution
31
+ name `iops-crashlab` and Apache-2.0 were selected for local candidate 0.1.0a1.
32
+
33
+ ## Local use
34
+
35
+ Requirements: Python 3.11+, uv, a running local Unix-socket Linux Docker engine
36
+ with cgroup v2 and default seccomp, and the pinned official Python base image.
37
+ The verified setup is Docker Desktop Engine 29.8.0 on macOS arm64. Other host
38
+ platforms/engine versions have not been verified.
39
+
40
+ ```sh
41
+ export UV_CACHE_DIR="$PWD/../../.cache/uv"
42
+ uv sync --python 3.12 --frozen
43
+ uv run --frozen iops-crashlab examples
44
+ uv run --frozen iops-crashlab prepare --pull
45
+ uv run --frozen iops-crashlab validate builtin:protected-file-write
46
+ uv run --frozen iops-crashlab test builtin:protected-file-write --output /tmp/crashlab-protected
47
+ uv run --frozen iops-crashlab report /tmp/crashlab-protected/result.json --verify-artifacts
48
+ ```
49
+
50
+ Use a new output directory. `prepare --pull` explicitly downloads the pinned
51
+ base image and builds a minimal reference image locally; normal runs never pull.
52
+ No repository directory, Docker socket, secret store or host environment is
53
+ mounted or forwarded into the container. The product does not start Docker
54
+ Desktop or alter engine configuration automatically.
55
+
56
+ ```sh
57
+ # A controlled violating test subject: expected exit 1 / failed state assertion.
58
+ uv run --frozen iops-crashlab test builtin:protected-file-write --reference-variant violate --output /tmp/crashlab-protected-failed
59
+ # Fake API: mutate, return a synthetic 503, retry idempotently; no real service.
60
+ uv run --frozen iops-crashlab test builtin:fake-api-partial-failure --output /tmp/crashlab-retry
61
+ # Known timeout subject: expected failed budget assertion and confirmed cleanup.
62
+ uv run --frozen iops-crashlab test builtin:execution-budget --reference-variant timeout --output /tmp/crashlab-timeout
63
+ ```
64
+
65
+ A JSON scenario path can replace a builtin name. The examples in `scenarios/`
66
+ are independently authored local inputs; they need no Permission Simulator.
67
+ Unknown adapters/fixtures/faults/assertion meanings are unsupported before
68
+ execution. The current verifier registry requires exact documented assertion
69
+ IDs, methods and expectations; it does not interpret prose as executable logic.
70
+ `--skip` records deliberate nonexecution without touching Docker.
71
+
72
+ Exit codes for test: passed 0, failed 1, error/input/I/O 2, inconclusive 3,
73
+ unsupported 4, skipped 5. A harness/cleanup failure is error, not an agent failure.
74
+ A denied synthetic tool call may be the expected behavior of a passing case.
75
+ Tool scenarios require selected-action evidence to pass. Command scenarios can
76
+ pass a process-status assertion without claiming internal tool visibility.
77
+ Reports retain provenance;
78
+ reference/mock imported results are clearly labeled as non-executed evidence.
79
+
80
+ ## The first run
81
+
82
+ A bare `iops-crashlab`, run by a person at a terminal, opens a short wizard:
83
+ prepare the pinned reference image, choose a scenario, run it, and see the
84
+ verdict. Arrow keys choose, Enter confirms, Esc cancels and exits 130 having
85
+ changed nothing. The preselected answer to every question is the one that neither
86
+ writes a report directory nor pulls an image, so holding Enter down finishes the
87
+ wizard without touching the machine. A verdict keeps its declared exit code, so a
88
+ skipped, unsupported or inconclusive run is never reported here as a pass.
89
+
90
+ A bare run that is **not** a person is unchanged. The wizard needs both stdin and
91
+ stdout to be terminals, no `CI` in the environment, `TERM` other than `dumb`, and
92
+ no `IOPS_NO_WIZARD`; anything else prints the `invalid_arguments` JSON error and
93
+ exits 2, exactly as before. These commands run inside CI and inside other agents'
94
+ harnesses, where a prompt is a hang and a hang is worse than an error. Set
95
+ `IOPS_NO_WIZARD=1` to suppress it on an interactive terminal, and `NO_COLOR` to
96
+ drop the colour. Every subcommand, flag, JSON payload and exit code is untouched.
97
+
98
+ ## Python API
99
+
100
+ ```python
101
+ from iops_crashlab import loads_document, prepare_reference_image, run_scenario
102
+ from iops_crashlab.cli import load_scenario
103
+
104
+ # Run prepare_reference_image(pull=True) once if the pinned base is not cached.
105
+ scenario = load_scenario("builtin:protected-file-write")
106
+ result = run_scenario(scenario, "/tmp/crashlab-python-case")
107
+ assert result["provenance"] == "observed"
108
+ print(result["verdict"])
109
+ ```
110
+
111
+ `loads_document` / `validate_document` still implement all four shared data
112
+ contracts. Malformed inputs raise redacted ContractValidationError. Unsupported
113
+ runtime requirements produce a validated unsupported Evidence Result. Output
114
+ creation refuses an existing directory and uses private permissions.
115
+
116
+ ## Custom agents
117
+
118
+ Use an explicit bundle definition; it selects files rather than mounting a source
119
+ directory. Builds copy bytes and run no agent code or package installation.
120
+
121
+ Unlike the commands above, which name packaged `builtin:` scenarios, the bundles
122
+ and scenarios below are example files in this repository and are not included in
123
+ an installed package. Point these commands at your own bundle, or run them from a
124
+ checkout.
125
+
126
+ ```sh
127
+ uv run --frozen iops-crashlab describe-agent examples/python-agent/agent.json
128
+ uv run --frozen iops-crashlab prepare --agent examples/python-agent/agent.json
129
+ uv run --frozen iops-crashlab test examples/python-agent/protected-scenario.json --agent examples/python-agent/agent.json --output /tmp/crashlab-custom
130
+ # Negative control: this agent bypasses the tool denial and changes the file.
131
+ uv run --frozen iops-crashlab prepare --agent examples/python-agent/bypass.json
132
+ uv run --frozen iops-crashlab test examples/python-agent/protected-scenario.json --agent examples/python-agent/bypass.json --output /tmp/crashlab-bypass
133
+ # Generic commands expose exit/time/output status, not their internal tool calls.
134
+ uv run --frozen iops-crashlab prepare --agent examples/command-agent/agent.json
135
+ uv run --frozen iops-crashlab test examples/command-agent/scenario.json --agent examples/command-agent/agent.json --output /tmp/crashlab-command
136
+ ```
137
+
138
+ The Python API adds `load_agent(path)`, `prepare_agent_image(bundle)`, and
139
+ `run_scenario(scenario, output, agent=bundle)`. A changed source bundle requires
140
+ preparation of its new image. Target IDs and required features must match the
141
+ selected adapter; unsupported requirements never fall back to a reference agent.
142
+ See [AGENT_GUIDE.md](https://github.com/i-ops-hq/labs/blob/main/products/crashlab/AGENT_GUIDE.md) for the input format, tool API, retry example,
143
+ visibility limits and file/image-cache handling.
144
+
145
+ ## Reclaiming what it builds
146
+
147
+ Every image this product builds carries the label `org.iops.crashlab.bundle-sha256`,
148
+ and an agent image additionally carries `org.iops.crashlab.agent-sha256`. That label
149
+ is what makes a safe, exact cleanup expressible, because it matches this product's
150
+ images and nothing else on the machine:
151
+
152
+ ```sh
153
+ docker image prune --filter label=org.iops.crashlab.bundle-sha256
154
+ ```
155
+
156
+ `prepare` is content-addressed, so preparing the same bundle twice builds nothing the
157
+ second time and running the same scenario repeatedly costs one image, not one per run.
158
+ What does accumulate is editing an agent bundle: each distinct bundle is a distinct
159
+ image, so a cycle of edit-and-prepare leaves one behind per edit. Containers are
160
+ removed as each run ends and the run report records whether that cleanup was
161
+ confirmed; images are deliberately kept, because rebuilding them is the slow part.
162
+
163
+ ## Evidence and security boundaries
164
+
165
+ Each report directory contains result.json, report.md, events.jsonl, state.json
166
+ and execution.json. Assertions link to actual artifacts with computed SHA-256.
167
+ The independent verifier reads bounded regular guest files without following
168
+ symlinks, measures process/cgroup controls, and the host evaluates predicates.
169
+ Tool success and agent text are not accepted as proof of state. Hash checks
170
+ establish local consistency, not authenticity of an arbitrary imported result.
171
+
172
+ No host mounts; network off; UID/GID 65532; read-only root; all capabilities
173
+ dropped; no-new-privileges; finite CPU/memory/swap/PIDs; bounded tmpfs, logs,
174
+ subprocess output, deadlines and owned-container cleanup. Reference tools can be
175
+ bypassed by the violating subject inside its disposable fixture; this is a test
176
+ of declared tool policy, not proof of OS-enforced authority. See
177
+ [RUNNER_SPEC.md](https://github.com/i-ops-hq/labs/blob/main/products/crashlab/RUNNER_SPEC.md) and repository security/validation documents.
178
+
179
+ ```sh
180
+ # Unit/validation tests only: real integration cases are explicitly skipped.
181
+ uv run --frozen pytest -q
182
+ # Requires the actual local engine/base; missing prerequisites fail this check.
183
+ uv run --frozen pytest -q --docker
184
+ ```
185
+
186
+ Unit fault injections are explicitly simulated and are not advertised as real
187
+ integrations. The separate live suite tests pass/fail consequences, retry state,
188
+ time/output budgets, guest controls, host-secret omission, no-action handling
189
+ and confirmed cleanup. No model API, real customer service, production endpoint,
190
+ remote Docker host, VM manager or cloud service is supported here.