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 +68 -14
- package/WORKFLOW.md +327 -64
- package/agents/work-reviewer.md +1 -1
- package/bin/factory.js +196 -33
- package/bin/restore.js +320 -0
- package/bin/snapshot.js +130 -0
- package/core/atomic-write.js +66 -54
- package/core/contracts.js +48 -7
- package/core/effective-push.js +5 -5
- package/core/run-lock.js +14 -4
- package/core/write-core.js +1 -1
- package/observe/repository-config.js +12 -2
- package/package.json +1 -1
- package/state/review-archive.js +36 -22
- package/state/schema.js +85 -4
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
|
|
41
|
-
`bootstrap`, and `bootstrap_timeout_ms`.
|
|
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`;
|
|
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
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
`
|
|
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
|
|
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
|
|