Skip to content

Commit 01eae09

Browse files
authored
feat(factory): add safe Kit failure diagnostics (#187)
* docs(factory): design safe failure diagnostics * feat(factory): define safe diagnostic manifest * feat(factory): project safe Kit runtime events * fix(factory): enforce Kit child lifecycle * feat(factory): classify safe failure diagnostics * feat(factory): preserve safe runtime diagnostics * fix(factory): preserve primary failure diagnostics * feat(factory): upload safe failure diagnostics * test(factory): enforce exact diagnostics upload contract * test(factory): count all diagnostics upload refs * fix(factory): harden safe diagnostic bundle * test(factory): assert stale cleanup failure statuses
1 parent 892d864 commit 01eae09

15 files changed

Lines changed: 1833 additions & 72 deletions

.github/workflows/guide-draft.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,15 @@ jobs:
122122
"$RUNNER_TEMP/export"
123123
cp "$RUNNER_TEMP/export/run-report.json" "$RUNNER_TEMP/run-report.json"
124124
125+
- name: Upload safe factory diagnostics
126+
if: failure() && steps.kit.outcome == 'failure'
127+
uses: actions/upload-artifact@v4
128+
with:
129+
name: guide-factory-diagnostics-${{ github.run_id }}-${{ github.run_attempt }}
130+
path: ${{ runner.temp }}/export/factory-diagnostics.json
131+
retention-days: 7
132+
if-no-files-found: ignore
133+
125134
- name: Validate export
126135
id: validate
127136
if: success() && steps.refusal.outcome != 'success'
Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
# Kit Safe Failure Bundle Implementation Plan
2+
3+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4+
5+
**Goal:** Preserve a downloadable, seven-day GitHub Actions failure artifact containing only validated Kit tool lifecycle metadata and deterministic post-mortem classification.
6+
7+
**Architecture:** Enable pinned Kit's private runtime-event side channel and stream it through a fail-closed projector that suppresses unsafe summaries and identifiers. Assemble one exact diagnostic manifest inside/around the container, validate it again on the host, and upload only that file on failed Kit runs. Raw transcripts are never read.
8+
9+
**Tech Stack:** Bash 5, jq, Docker, Kit v0.1.98 runtime events, GitHub Actions upload-artifact v4, existing factory shell test harness.
10+
11+
**Spec:** `docs/superpowers/specs/2026-08-28-kit-safe-failure-bundle-design.md`
12+
13+
## Global Constraints
14+
15+
- Kit remains pinned to `0.1.98` with SHA-256 `7d14561469ced8af21df1075a9071d04a7bad1b1c5ff90d685142d3231abae85`.
16+
- Never preserve or emit raw transcripts, runtime summaries, prompts, tool arguments/results, commands, URLs, content, environment values, credentials, session IDs, raw call IDs, or arbitrary errors.
17+
- Runtime event count is capped at 512; duration is capped at 10,800,000 ms.
18+
- Diagnostics are uploaded only for failed Kit steps and retained for exactly seven days.
19+
- Kit receives no GH token, Pulse credentials, SSH credentials, or unrelated Actions secrets.
20+
- Tests make no paid OpenRouter/Exa calls and use no real Pulse credentials.
21+
22+
## File map
23+
24+
- Create `factory/schemas/factory-diagnostics.schema.json`: exact public artifact contract.
25+
- Create `factory/scripts/project-kit-events.sh`: streaming marked-event suppression and safe lifecycle projection.
26+
- Create `factory/scripts/validate-diagnostics.sh`: exact recursive manifest and lifecycle validator.
27+
- Create `factory/scripts/build-diagnostics.sh`: deterministic classification and atomic manifest assembly.
28+
- Create `factory/tests/test-diagnostics.sh`: adversarial projector, builder, validation, and non-leakage coverage.
29+
- Modify `factory/scripts/container-entrypoint.sh`: FIFO capture, Kit lifecycle, safe export.
30+
- Modify `factory/scripts/run-kit.sh`: stale removal, build-failure synthesis, host validation, minimal logging.
31+
- Modify `factory/tests/test-container.sh`: container/host integration and stale/non-leakage regressions.
32+
- Modify `.github/workflows/guide-draft.yml`: upload the one validated failure artifact for seven days.
33+
- Modify `factory/tests/test-coordinator.sh`: workflow contract assertions.
34+
- Modify `factory/README.md`: operator download and interpretation instructions.
35+
36+
---
37+
38+
### Task 1: Define and validate the safe diagnostic schema
39+
40+
**Files:**
41+
- Create: `factory/schemas/factory-diagnostics.schema.json`
42+
- Create: `factory/scripts/validate-diagnostics.sh`
43+
- Create: `factory/tests/test-diagnostics.sh`
44+
45+
**Interfaces:**
46+
- Consumes: one manifest path.
47+
- Produces: `validate-diagnostics.sh <manifest>` with no stdout on success and nonzero failure using fixed `factory: invalid diagnostics` stderr.
48+
49+
- [ ] **Step 1: Write failing exact-schema tests**
50+
51+
Create fixtures in `test-diagnostics.sh` with exact top-level keys `schema_version`, `kind`, `status`, `stage`, `classification`, `report`, `events`, and `kit_errors`. Assert rejection of extra keys, strings where booleans/integers are required, unknown enums, more than 512 events, durations over `10800000`, noncontiguous sequences, duplicate starts/finishes, finish-before-start, mismatched tools/operations for one `call_ref`, and the malicious nested key `events[0].details.prompt`. Include the existing safe Kit fatal record shape.
52+
53+
- [ ] **Step 2: Run the test and confirm RED**
54+
55+
Run: `bash factory/tests/test-diagnostics.sh`
56+
Expected: failure because the validator and schema do not exist.
57+
58+
- [ ] **Step 3: Add the JSON Schema and recursive validator**
59+
60+
Use draft 2020-12 with `additionalProperties: false` at every object. In `validate-diagnostics.sh`, reject symlinks, run `jq -e` for exact recursive types/enums, then fold events in sequence order to enforce lifecycle consistency and caps. Emit only `factory: invalid diagnostics` on any rejection; never echo source JSON or jq errors.
61+
62+
- [ ] **Step 4: Run focused tests and ShellCheck**
63+
64+
Run: `bash factory/tests/test-diagnostics.sh && mise x shellcheck@0.10.0 -- shellcheck factory/scripts/validate-diagnostics.sh factory/tests/test-diagnostics.sh`
65+
Expected: PASS and no ShellCheck output.
66+
67+
- [ ] **Step 5: Commit**
68+
69+
```bash
70+
git add factory/schemas/factory-diagnostics.schema.json factory/scripts/validate-diagnostics.sh factory/tests/test-diagnostics.sh
71+
git commit -m "feat(factory): define safe diagnostic manifest"
72+
```
73+
74+
### Task 2: Project Kit runtime events without retaining summaries
75+
76+
**Files:**
77+
- Create: `factory/scripts/project-kit-events.sh`
78+
- Modify: `factory/tests/test-diagnostics.sh`
79+
80+
**Interfaces:**
81+
- Consumes: Kit stderr on stdin and one output JSON path.
82+
- Produces: ordinary stderr forwarded to stderr plus atomic JSON array at the output path; marked lines never forwarded.
83+
84+
- [ ] **Step 1: Add failing projector fixtures**
85+
86+
Use the exact marker byte followed by v0.1.98 `child_started`, `child_finished`, `session_started`, `compaction_started`, and `compaction_finished` JSON. Put unique canaries in `summary`, `call`, `session_id`, `reason`, command, prompt, URL, and output. Assert call refs become integers, sequence is contiguous, known anchored commands classify to stable operations, unknown shell commands become `unrecognized`, unknown tool names become `unknown`, success/duration project correctly, session and compaction events disappear, and no canary appears in stdout, stderr, or output. Add malformed/unknown event tests that remove partial output while continuing to drain input.
87+
88+
- [ ] **Step 2: Run the projector tests and confirm RED**
89+
90+
Run: `bash factory/tests/test-diagnostics.sh`
91+
Expected: failure because `project-kit-events.sh` does not exist.
92+
93+
- [ ] **Step 3: Implement streaming projection**
94+
95+
Read with `IFS= read -r`. Forward unmarked lines with `printf '%s\n' >&2`. For marked lines, parse into a temporary file with jq, validate exact source event fields/types, maintain raw-call-to-integer mapping only in temporary state, classify allowlisted tool/operation enums, and atomically rename the final array. Trap cleanup removes all temporary files. Never print a marked source line or parse error.
96+
97+
- [ ] **Step 4: Verify projection and non-leakage**
98+
99+
Run: `bash factory/tests/test-diagnostics.sh && mise x shellcheck@0.10.0 -- shellcheck factory/scripts/project-kit-events.sh factory/tests/test-diagnostics.sh`
100+
Expected: PASS; grep assertions find no canary.
101+
102+
- [ ] **Step 5: Commit**
103+
104+
```bash
105+
git add factory/scripts/project-kit-events.sh factory/tests/test-diagnostics.sh
106+
git commit -m "feat(factory): project safe Kit runtime events"
107+
```
108+
109+
### Task 3: Build deterministic post-mortem manifests
110+
111+
**Files:**
112+
- Create: `factory/scripts/build-diagnostics.sh`
113+
- Modify: `factory/tests/test-diagnostics.sh`
114+
115+
**Interfaces:**
116+
- Consumes: stage, Kit exit status, event array, optional validated report, optional validated safe Kit error summary, and output path.
117+
- Produces: atomically written, validator-approved `factory-diagnostics.json`.
118+
119+
- [ ] **Step 1: Add failing classification tests**
120+
121+
Cover exact precedence: Docker build failure → `docker_build_failed`; container start/run failure → `container_run_failed`; nonempty validated Kit fatal records → `kit_fatal`; nonzero Kit with no fatal → `kit_prompt_failed`; zero Kit with no report → `missing_run_report`; present invalid report → `invalid_run_report`; validated report outcome `failed``factory_reported_failure`. Assert report projection contains only `exists`, `valid`, `outcome`, and `review_rounds`.
122+
123+
- [ ] **Step 2: Run tests and confirm RED**
124+
125+
Run: `bash factory/tests/test-diagnostics.sh`
126+
Expected: failure because `build-diagnostics.sh` does not exist.
127+
128+
- [ ] **Step 3: Implement atomic assembly**
129+
130+
Validate every optional input before jq composition. Use fixed enum arguments rather than free-form messages. Project only validated report scalars and existing allowlisted Kit fatal fields. Run `validate-diagnostics.sh` on the sibling temporary output before `mv`. On any error remove temporary/final output and emit only `factory: diagnostics unavailable`.
131+
132+
- [ ] **Step 4: Verify classifications**
133+
134+
Run: `bash factory/tests/test-diagnostics.sh && git diff --check`
135+
Expected: PASS and no whitespace errors.
136+
137+
- [ ] **Step 5: Commit**
138+
139+
```bash
140+
git add factory/scripts/build-diagnostics.sh factory/tests/test-diagnostics.sh
141+
git commit -m "feat(factory): classify safe failure diagnostics"
142+
```
143+
144+
### Task 4: Integrate FIFO capture and host validation
145+
146+
**Files:**
147+
- Modify: `factory/scripts/container-entrypoint.sh`
148+
- Modify: `factory/scripts/run-kit.sh`
149+
- Modify: `factory/tests/test-container.sh`
150+
151+
**Interfaces:**
152+
- Consumes: projector, builder, validator, Kit stderr, and normal run outputs.
153+
- Produces: `/export/factory-diagnostics.json` when and only when safe validation succeeds.
154+
155+
- [ ] **Step 1: Add failing container/host tests**
156+
157+
Extend fake Kit fixtures to emit interleaved marked events with secret summaries, ordinary stderr, success, nonzero failure, and zero exit without a report. Assert ordinary stderr remains, marked lines/canaries never appear, missing-report classification is preserved, safe fatal records remain exact, stale diagnostics are deleted before Docker build, and malformed container diagnostics are deleted rather than logged. Add a fake Docker build failure expecting a minimal `docker_build_failed` manifest with no child events.
158+
159+
- [ ] **Step 2: Run focused tests and confirm RED**
160+
161+
Run: `bash factory/tests/test-container.sh`
162+
Expected: failure because runtime capture/host preservation is absent.
163+
164+
- [ ] **Step 3: Integrate the FIFO in `container-entrypoint.sh`**
165+
166+
Create a private FIFO and background projector, run Kit with `KIT_RUNTIME_EVENTS=1` and stderr redirected to the FIFO, capture Kit status without `set -e` aborting, wait for projector status, append safe call-ref-zero Kit events, validate report state, assemble diagnostics, and preserve existing guide/report export behavior. Traps remove FIFO and temporary files.
167+
168+
- [ ] **Step 4: Integrate host-side handling in `run-kit.sh`**
169+
170+
Delete stale diagnostics before build. Synthesize the fixed Docker-build manifest when no container can run. For container diagnostics, call the exact validator before retaining the file. Print at most `factory: diagnostics: stage=<enum> classification=<enum> events=<integer>`. Never print manifest JSON.
171+
172+
- [ ] **Step 5: Verify container regressions**
173+
174+
Run: `bash factory/tests/test-container.sh && mise x shellcheck@0.10.0 -- shellcheck factory/scripts/*.sh factory/tests/test-container.sh`
175+
Expected: PASS and no ShellCheck output.
176+
177+
- [ ] **Step 6: Commit**
178+
179+
```bash
180+
git add factory/scripts/container-entrypoint.sh factory/scripts/run-kit.sh factory/tests/test-container.sh
181+
git commit -m "feat(factory): preserve safe runtime diagnostics"
182+
```
183+
184+
### Task 5: Upload the validated failure artifact
185+
186+
**Files:**
187+
- Modify: `.github/workflows/guide-draft.yml`
188+
- Modify: `factory/tests/test-coordinator.sh`
189+
- Modify: `factory/README.md`
190+
191+
**Interfaces:**
192+
- Consumes: `$RUNNER_TEMP/export/factory-diagnostics.json`.
193+
- Produces: Actions artifact `guide-factory-diagnostics-${{ github.run_id }}-${{ github.run_attempt }}` retained seven days.
194+
195+
- [ ] **Step 1: Add failing workflow contract assertions**
196+
197+
Assert one `actions/upload-artifact@v4` step named `Upload safe factory diagnostics`, condition `failure() && steps.kit.outcome == 'failure'`, exact single-file path, `retention-days: 7`, `if-no-files-found: ignore`, and the run-ID/run-attempt artifact name. Assert the issue failure step does not read or inline diagnostics.
198+
199+
- [ ] **Step 2: Run workflow contract tests and confirm RED**
200+
201+
Run: `bash factory/tests/test-coordinator.sh`
202+
Expected: failure because the upload step is absent.
203+
204+
- [ ] **Step 3: Add the upload step and operator docs**
205+
206+
Place upload after `Run Kit` and before failure reporting with `if: failure() && steps.kit.outcome == 'failure'`. Document:
207+
208+
```bash
209+
gh run download RUN_ID \
210+
--repo speakeasy-api/mcp-setup-docs \
211+
--name guide-factory-diagnostics-RUN_ID-RUN_ATTEMPT
212+
jq . factory-diagnostics.json
213+
```
214+
215+
State that artifacts are repository-access-controlled, expire after seven days, and contain no raw transcript or tool payloads.
216+
217+
- [ ] **Step 4: Run workflow checks**
218+
219+
Run: `bash factory/tests/test-coordinator.sh && actionlint .github/workflows/guide-draft.yml`
220+
Expected: PASS and no actionlint findings.
221+
222+
- [ ] **Step 5: Commit**
223+
224+
```bash
225+
git add .github/workflows/guide-draft.yml factory/tests/test-coordinator.sh factory/README.md
226+
git commit -m "feat(factory): upload safe failure diagnostics"
227+
```
228+
229+
### Task 6: Final security and regression verification
230+
231+
**Files:**
232+
- Modify only if verification exposes a defect.
233+
234+
**Interfaces:**
235+
- Consumes: complete implementation.
236+
- Produces: review-ready branch with offline evidence.
237+
238+
- [ ] **Step 1: Run the complete factory suite**
239+
240+
Run: `GOFLAGS='-ldflags=-linkmode=external' bash factory/tests/run.sh`
241+
Expected: every `test-*.sh` file passes, including `test-diagnostics.sh`.
242+
243+
- [ ] **Step 2: Run static checks**
244+
245+
Run:
246+
247+
```bash
248+
mise x shellcheck@0.10.0 -- shellcheck factory/scripts/*.sh factory/tests/*.sh
249+
actionlint .github/workflows/guide-draft.yml
250+
git diff --check
251+
```
252+
253+
Expected: all commands exit zero with no findings.
254+
255+
- [ ] **Step 3: Run bounded canary scan**
256+
257+
Run: `rg -n 'SECRET_CANARY|PROMPT_CANARY|URL_CANARY|SESSION_CANARY|CALL_CANARY' factory/tests/test-diagnostics.sh factory/tests/test-container.sh` and confirm every canary appears only in input fixtures/assertions. Run the tests once more and confirm none appears in captured stdout, stderr, or output artifacts.
258+
259+
- [ ] **Step 4: Request independent security review**
260+
261+
Ask the reviewer to inspect the full diff for raw-event leakage, malformed-event fail-open behavior, stale artifact reuse, workflow over-upload, lifecycle validation gaps, and any path that could print manifest/source JSON. Fix every Critical or Important finding and rerun Steps 1–3.
262+
263+
- [ ] **Step 5: Commit verification fixes if needed**
264+
265+
```bash
266+
git add factory .github/workflows/guide-draft.yml
267+
git commit -m "fix(factory): harden safe diagnostic bundle"
268+
```

0 commit comments

Comments
 (0)