feature-factory 0.8.0 → 0.8.2

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 (3) hide show
  1. package/README.md +9 -0
  2. package/WORKFLOW.md +58 -0
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -106,6 +106,15 @@ workflow, outcome, or acceptance criteria, so it reaches Gate 1 with nothing to
106
106
  non-zero exit instead refuses immediately, names the reference, and creates no session or run. Only the
107
107
  resolver author knows which of the two cases it is in, so the contract cannot make the choice for it.
108
108
 
109
+ A `needs-human` terminalization is followed by a control-plane snapshot: the driver copies the run
110
+ directory to `$O/.factory/.parked/<R>` immediately after recording the park. A parked run waits for
111
+ outside intervention, and its control plane otherwise exists only inside the sandbox, so anything that
112
+ removed the sandbox destroyed the manifest and every accepted gate with it. `.parked` cannot be a run
113
+ id, so a snapshot never occupies the completed archive at `$O/.factory/<R>` and never blocks
114
+ re-initialising the same run id. It is published by a staged, verified swap, so a failed later park cannot degrade the last good
115
+ snapshot. A failed snapshot is reported and never prevents the park. `blocked`
116
+ and `partial` are not snapshotted, and a snapshot is evidence for recovery rather than a resumable run.
117
+
109
118
  Malformed config, malformed payload, a non-zero exit, or unavailable exit status refuses before any
110
119
  run effect and never falls back:
111
120
 
package/WORKFLOW.md CHANGED
@@ -149,6 +149,9 @@ An inability to ask a human never promotes interactive or headless to autonomous
149
149
 
150
150
  Mode result needs-human means parked and explicitly resumable; only completed, partial, and blocked are final.
151
151
  Enter the parked stop with factory terminal R needs-human --reason TEXT; leave it only by explicit factory resume R --session $SESSION_ID --repo S, which refuses unless that session already holds a fresh lock: claim, then verify, then resume.
152
+ Immediately after recording a `needs-human` terminalization, and before reporting the park to the operator,
153
+ publish a parked control-plane snapshot exactly as *Parked control-plane snapshot* below requires. A park
154
+ is not reported until that snapshot is published or its failure is recorded in the report.
152
155
  For top-level needs-human, status exposes the durable next action, but no command may execute it before explicit factory resume.
153
156
  Report top-level needs-human as parked with its reason and explicit factory resume command.
154
157
  Retain the sandbox for top-level needs-human while parked, then explicitly resume it after the external fix.
@@ -188,6 +191,61 @@ an observably clean tree: `worktree_clean` records false, the suite is skipped,
188
191
  classifies `unavailable`, which parks the run. Ignoring the root paths instead is not the fix — `.gitignore`
189
192
  is privileged precisely because ignoring a file conceals it from these checks.
190
193
 
194
+ ### Parked control-plane snapshot
195
+
196
+ A parked run waits for outside intervention, and its control plane otherwise exists only inside the
197
+ sandbox: the completed handoff is the only thing that archives it, and that handoff is entered only for
198
+ `completed`. So anything that removed the sandbox destroyed the manifest, the approved gates, the ratified
199
+ plan and every review verdict, leaving the run neither resumable nor reconstructable.
200
+
201
+ Publish the live plane `P` to `$O/.factory/.parked/$R`. Inspect `$O/.factory` and `$O/.factory/.parked` with
202
+ non-following metadata reads, creating each missing parent one directory at a time and requiring any present
203
+ one to be a real directory rather than a symbolic link. Never write through a symlinked parent, and never
204
+ write anywhere but under `$O/.factory/.parked`.
205
+
206
+ **Publication has exactly one commit point: the rename that puts a verified staging tree onto the canonical
207
+ path.** Every failure rule below is stated relative to it, because "leave the previous snapshot untouched"
208
+ and "clean up the prior copy" are contradictory instructions once that rename has happened.
209
+
210
+ 1. **Preflight.** Require no entry at `$O/.factory/.parked/.staging-$R`. A residual
211
+ `$O/.factory/.parked/.prior-$R` is the trace of an earlier publication whose cleanup did not finish, not
212
+ a snapshot to preserve: remove it before staging, and if that removal fails, report it and stop without
213
+ touching the canonical snapshot or staging anything.
214
+ 2. **Stage.** Copy `P` to `.staging-$R`, preserving every entry, its mode, and symlinks as symlinks. `W` is
215
+ outside `P`; do not copy slice worktrees or any other part of `S`.
216
+ 3. **Verify.** Build source and destination inventories exactly as the completed archive does — every
217
+ entry's relative path, type and mode, a SHA-256 for each regular file, a link target for each symlink,
218
+ sorted lexically — and require exact equality. An unverified staging tree is never published.
219
+ 4. **Commit.** With no snapshot at the canonical path, rename `.staging-$R` onto it; that rename is the
220
+ commit point. With one present, first rename the canonical snapshot to `.prior-$R`, then rename
221
+ `.staging-$R` onto the canonical path; that second rename is the commit point. If the first rename
222
+ succeeds and the second fails, rename `.prior-$R` back onto the canonical path and report: nothing was
223
+ committed.
224
+ 5. **Before the commit point, no publication has occurred.** Any failure leaves the previous canonical
225
+ snapshot in place — restoring it from `.prior-$R` when it had already been moved — removes only
226
+ `.staging-$R`, and records the failure in the park report.
227
+ 6. **After the commit point, the published snapshot is authoritative and is never rolled back.** Removing
228
+ `.prior-$R` is cleanup, not part of publication: if it fails, report a cleanup warning naming the
229
+ residual path and leave the published snapshot exactly as committed. A later park removes that residual
230
+ at preflight, as step 1 requires.
231
+
232
+ `.staging-$R` and `.prior-$R` cannot be run ids, so neither is ever a manifest candidate.
233
+
234
+ Three properties make this safe to do at a park rather than only at completion:
235
+
236
+ 1. `.parked` cannot be a run id, so a snapshot never occupies the completed archive at `$O/.factory/$R` and
237
+ never becomes a manifest candidate. A run may park, resume, and later complete with its handoff archive
238
+ unaffected, and relaunching the same run id is not blocked by a snapshot left behind.
239
+ 2. It never touches `S`. The snapshot is a copy out; removal remains the completed handoff's guarded step
240
+ and nothing else's.
241
+ 3. A snapshot failure is reported and does not prevent or undo the park. Refusing to park because a copy
242
+ failed would leave `status: running` with nothing alive, which every health signal misreads — a worse
243
+ outcome than a missing snapshot.
244
+
245
+ A snapshot is evidence for recovery, not a resumable run: resume operates on the sandbox manifest. Do not
246
+ publish one for `blocked` or `partial`, which are not resumable, and never treat a snapshot as authority
247
+ over the live plane.
248
+
191
249
  At every interactive gate, `changes: <feedback>` records `changes`, follows
192
250
  `changes-at-gate:<name>`, revises only the affected stage, and re-presents it pending. `stop` requires
193
251
  qualified status `next: stopped-at-gate:<name>` and releases the driver's lock. This is an unlocked
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "feature-factory",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
4
4
  "description": "Durable, observed control plane for /feature runs. Host-agnostic: no opencode dependency.",
5
5
  "type": "module",
6
6
  "license": "MIT",