feature-factory 0.9.2 → 0.10.1

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.
package/README.md CHANGED
@@ -20,6 +20,16 @@ host binding and copies this workflow beside that skill at build/pack time. Inst
20
20
  `opencode-feature-factory` or `prime-agent-feature-factory` for a supported host, or build an adapter
21
21
  that loads the complete workflow and drives all durable state changes through the CLI.
22
22
 
23
+ ## Infrastructure failures and attempts
24
+
25
+ A confirmed host-origin availability or transport failure does not spend a specialist attempt. The active
26
+ driver permits one same-attempt recovery for the canonical role and subject only when the host proves
27
+ execution never started or can recover the same child/session. Unbudgeted research and design create no
28
+ attempt. A second consecutive failure parks the run. The count is memory-only and resets with the driver,
29
+ but resume and reset never prove that prior work did not start: the next driver must recover or prove the
30
+ same prior invocation safe before dispatch. Child text, unknown errors, and excluded auth, quota,
31
+ rate-limit, and configuration failures never trigger a free retry or duplicate possibly-started work.
32
+
23
33
  ## Repository command configuration
24
34
 
25
35
  A repository operator may provide optional `$O/.factory.json`, where `O` is the physically
@@ -37,9 +47,9 @@ resolved Git top level:
37
47
  }
38
48
  ```
39
49
 
40
- The root has three required properties and four optional properties: `pr_draft`, `verify_timeout_ms`,
41
- `bootstrap`, and `bootstrap_timeout_ms`. `resolve`, `verify`, `publish`, and a present `bootstrap` are non-empty command
42
- strings. There is no `publishing_identity` key, and a file carrying one is malformed because the
50
+ The root has two required properties, `resolve` and `verify`, and five optional properties: `publish`,
51
+ `pr_draft`, `verify_timeout_ms`, `bootstrap`, and `bootstrap_timeout_ms`. Required commands and any present
52
+ `publish` or `bootstrap` are non-empty strings. There is no `publishing_identity` key, and a file carrying one is malformed because the
43
53
  optional set is closed. A present `pr_draft` must be a JSON boolean and omission means `true`. Both timeouts are
44
54
  positive safe integers. `bootstrap_timeout_ms` requires `bootstrap`.
45
55
  Each omitted timeout independently defaults to `900000`; neither shares the other's budget. The file is
@@ -56,7 +66,7 @@ value stops the run instead of publishing under whatever credential the host hap
56
66
  it from `gh`, the token, stored authentication, or Git configuration: an expectation read from the
57
67
  credential being checked would always match.
58
68
 
59
- Validation refuses the first matching defect in this order: unreadable or invalid JSON, a non-object root, or unknown keys; invalid `pr_draft`; invalid `bootstrap`; `bootstrap_timeout_ms` without `bootstrap`; invalid `bootstrap_timeout_ms`; invalid `verify_timeout_ms`; then missing or invalid required entries.
69
+ Validation refuses the first matching defect in this order: unreadable or invalid JSON, a non-object root, or unknown keys; invalid `pr_draft`; invalid `bootstrap`; `bootstrap_timeout_ms` without `bootstrap`; invalid `bootstrap_timeout_ms`; invalid `verify_timeout_ms`; missing or invalid required entries; then invalid `publish`.
60
70
 
61
71
  The named forms are `.factory.json entry 'pr_draft' must be a boolean`, `.factory.json entry 'bootstrap' must be a non-empty string`, `.factory.json entry 'bootstrap_timeout_ms' requires a declared bootstrap command`, `.factory.json entry 'bootstrap_timeout_ms' must be a positive integer`, and `.factory.json entry 'verify_timeout_ms' must be a positive integer`.
62
72
 
@@ -113,10 +123,14 @@ removed the sandbox destroyed the manifest and every accepted gate with it. `.pa
113
123
  id, so a snapshot never occupies the completed archive at `$O/.factory/<R>` and never blocks
114
124
  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
125
  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
-
118
- Qualified status reports `park_snapshot` for a parked run: the published path, or `null` when no snapshot
119
- exists. That is how an outside observer verifies the snapshot happened rather than assuming it.
126
+ and `partial` are not snapshotted. A retained sandbox resumes directly; when that sandbox is lost,
127
+ `factory restore <R> --repo <O> --from refs/remotes/<remote>/<feature-branch>` rebuilds a parked,
128
+ lockless sandbox from the snapshot and the exact pushed feature ref. It reports every active slice reset
129
+ to `pending` rather than claiming branch-local work survived.
130
+
131
+ Qualified status reports `park_snapshot` for an unchanged live park: the published path, or `null` when no
132
+ matching snapshot exists. A restored generation deliberately reports `null` because its manifest and path
133
+ bindings changed; `status.restore` carries its source digest, feature commit, resets, and invalidations.
120
134
  Malformed config, malformed payload, a non-zero exit, or unavailable exit status refuses before any
121
135
  run effect and never falls back:
122
136
 
@@ -144,7 +158,7 @@ A failed, timed-out, dirty, or unobservable fresh init emits no JSON stdout, ret
144
158
 
145
159
  When both bootstrap keys are absent, init and resume are exact no-ops for bootstrap: no execution, manifest fields, output, or response-shape change.
146
160
 
147
- Bootstrap never runs during resolver intake, merge verification or replay, direct repository verification, slice observation, Gate 3, effective push, configured publication, push, or PR creation. Existing resolver, verify, configured-publish, effective-push, push, PR, and Gate 3 behavior is unchanged.
161
+ Bootstrap never runs during resolver intake, merge verification or replay, direct repository verification, slice observation, Gate 3, effective push, configured publication, push, or PR creation. Bootstrap does not mediate configured publication, effective-push, push, PR, or Gate 3 behavior.
148
162
 
149
163
  After a slice merge is successfully and atomically recorded, `verify` starts in the exact recorded
150
164
  integration worktree. Its configured string is submitted unchanged as one ordinary shell command with
@@ -190,6 +204,30 @@ Gate 3 always runs a separate fresh integrated `test-verifier` observation at th
190
204
  overwrites canonical evidence through the existing command mode and never shares, substitutes, or
191
205
  optimizes from post-merge evidence, even when the head is unchanged.
192
206
 
207
+ ## Restoring a lost parked sandbox
208
+
209
+ Run restore from the canonical operator repository, not from a replacement checkout:
210
+
211
+ ```sh
212
+ factory restore <run-id> --repo <operator-repository> \
213
+ --from refs/remotes/<remote>/<recorded-feature-branch> --json
214
+ ```
215
+
216
+ The source must be the canonical `$O/.factory/.parked/<run-id>` snapshot. The feature ref must be a full
217
+ remote-tracking ref whose branch suffix exactly matches the manifest and whose commit is still advertised
218
+ by that remote's push endpoint, exactly equal to the operator effective push endpoint. Restore validates
219
+ that snapshot, refuses symlinks that escape its control plane, clones the exact derived sandbox, aligns and
220
+ rechecks the operator's effective push target, and proves every preserved merged-slice Git and evidence
221
+ binding against the pushed head. It omits the old root session lock and publishes the transformed manifest
222
+ last. The run stays parked and no repository-configured bootstrap runs. Nonmerged slices with physical
223
+ branch or worktree state reset to `pending`, and the command reports them. Stale HEAD-bound validator
224
+ approval is invalidated. Gate 3 and test-verifier approval are always invalidated because canonical
225
+ publication evidence names the prior physical generation; canonical verifier records are omitted from the
226
+ copy. The staged slice projection must still match the Brief-bound plan digest and recorded amendments.
227
+ The durable `status.restore` record identifies the source inventory and restored commit. Because the new
228
+ generation has different paths and provenance, `status.park_snapshot` is `null` after a successful restore.
229
+ Claim the new lock and use the ordinary resume sequence only after reviewing those losses.
230
+
193
231
  ## Resuming a parked run
194
232
 
195
233
  A top-level `needs-human` status is a parked stop, not a final result. `completed`, `partial`, and
@@ -220,7 +258,18 @@ own the same path. Duplicate, target-already-owned, malformed, privileged, repla
220
258
  requests refuse atomically. Resume never amends or reseeds. A merge continues to refuse every unamended
221
259
  or privileged changed path.
222
260
 
223
- `resolve` and `verify` are consumed now, and the run's recorded `publishing_identity` is compared at the publication guards. Configured `publish` remains unconsumed and is not invoked.
261
+ A parked slice that exhausted its effective limit can receive exactly one audited extension through
262
+ `factory grant-retry <run-id> <slice-id> --scope slice|all --reason <text> --session <id> --repo <sandbox>`.
263
+ Slice scope raises only the target's additive allowance. All scope raises `max_retries` for pending later
264
+ waves too, but refuses when another slice is blocked or an exhausted post-merge repair record exists. Both
265
+ scopes reopen only the named slice after exact owner, snapshot, REJECT, evidence, base, current clean head,
266
+ and immutable attempt archives. A legacy run missing one prepares it without granting, then requires a
267
+ fresh snapshot and a second grant invocation. Exhaustion parks with reason `blocked-after-retries` rather
268
+ than terminalizing `partial`. The grant moves the prior canonical snapshot away, so restore cannot recover
269
+ pre-grant authority; run `factory snapshot <run-id> --repo <operator>` and requalify it before the separate
270
+ explicit resume. Restored blocked slices whose physical refs were intentionally cleared do not qualify.
271
+
272
+ `resolve` and `verify` are consumed now, and the run's recorded `publishing_identity` is compared at the publication guards. Step 6 resolves one selection: a nonblank inherited `FACTORY_PUBLISHING_COMMAND` selects its exact string; that variable set blank or whitespace selects the default; when it is unset, configured `publish` wins if present; otherwise the default wins. Only a selected nondefault command replaces `gh pr create`, after the factory-owned exact push and post-push identity guard. It receives exact `PR_BASE`, `FEATURE_BRANCH`, `PR_DRAFT`, `PR_TITLE`, and absolute `PR_BODY_FILE` environment values. Only exit zero with an absolute HTTPS URL on the last nonempty stdout line is recordable; every other result parks with exact reason `selected publishing command outcome indeterminate; re-observe whether the pull request exists before retry` and no fallback.
224
273
  Effective push-target capture and comparison are active through the package-owned `factory effective-push` command; they are not deferred to configured `publish`.
225
274
  The recorded `publishing_identity` is read from `status` exactly as reported, without trimming,
226
275
  normalization, case-folding, or reserialization. `init` refuses when neither the flag nor the environment
@@ -230,8 +279,8 @@ can report `null`. `publishing_identity` is a recorded run field reported by `st
230
279
  With a recorded identity, every mode checks it at exactly three boundaries: immediately
231
280
  after verified post-lock ownership, or immediately after an explicit resume is verified running with
232
281
  the same fresh owner and before reconciliation or other work; immediately before `git push`, after
233
- effective push-target equality; and immediately before `gh pr create`, after the push is known
234
- successful. No operation intervenes across a guard boundary. Only a manifest written before 0.8.0, which can
282
+ effective push-target equality; and immediately before the selected PR-creation command, after the
283
+ factory-owned exact push is known successful. No operation intervenes across a guard boundary. Only a manifest written before 0.8.0, which can
235
284
  report `null`, skips all three guards; an absent config does not affect them.
236
285
 
237
286
  Before each guard, inherited `GH_TOKEN` must exist and contain at least one character. Missing or empty
@@ -268,7 +317,7 @@ session.
268
317
 
269
318
  Publishing-identity verification is enforcement because it prevents false-green or wrong-account
270
319
  publication. Credential provisioning and helper setup are instruction only. Existing push,
271
- `gh pr create`, `factory pr`, Gate 3, merge, and approval semantics remain unchanged. The live config
320
+ `factory pr`, Gate 3, merge, and approval semantics remain unchanged. The live config
272
321
  is not part of this package and no generated config or resolver asset is shipped. See the repository's
273
322
  [operator guide](https://github.com/jasoncarreira/feature-factory/blob/main/OPERATING.md) for the
274
323
  shared inherited-token helper recipe; it does not acquire, store, install, or repair credentials.
@@ -285,7 +334,12 @@ repositories and compares them exactly. `check` freshly captures both targets an
285
334
  configuration. Both modes use shell-free Git subprocesses, write no output on success, and retain the
286
335
  sandbox on a fixed redacted failure. Captured targets and child diagnostics are never returned, logged,
287
336
  persisted in factory state, printed, or attached as an error cause. The command is independent of
288
- `publishing_identity`, adds no run state or flag, and configured `publish` remains unconsumed.
337
+ `publishing_identity` and adds no run state or flag. After the factory-owned exact push and post-push
338
+ identity guard, Step 6 resolves one PR-creation selection: a nonblank inherited
339
+ `FACTORY_PUBLISHING_COMMAND` selects its exact string; that variable set blank or whitespace selects the
340
+ default; when it is unset, configured `publish` wins if present; otherwise the default wins. Only the
341
+ selected nondefault command replaces `gh pr create`, so a repository declaration cannot leave a host with
342
+ nothing to delegate to unable to publish at all.
289
343
 
290
344
  ## Why the code exists at all
291
345