|
| 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