feature-factory 0.8.5 → 0.8.7
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/WORKFLOW.md +29 -11
- package/bin/factory.js +10 -1
- package/package.json +1 -1
package/WORKFLOW.md
CHANGED
|
@@ -177,7 +177,8 @@ reporting anything:
|
|
|
177
177
|
A park that completes only step 1 is an unreported park with no recovery evidence, which is the state
|
|
178
178
|
this sequence exists to prevent. Verify step 2 the way an outside observer would: qualified status
|
|
179
179
|
reports `park_snapshot` as the published path, or `null` when no snapshot exists. Status reports the path
|
|
180
|
-
only while the snapshot is a complete copy of the live plane by the step 3 inventory
|
|
180
|
+
only while the snapshot is a complete copy of the live plane by the step 3 inventory — `factory.lock`
|
|
181
|
+
excluded, since a heartbeat may land between the copy and the read — and its manifest still
|
|
181
182
|
matches the live one byte for byte, so `null` also covers an interrupted or altered copy and a snapshot
|
|
182
183
|
left by an earlier park: neither is evidence for this park. Publishing again is what makes it correspond.
|
|
183
184
|
|
|
@@ -243,7 +244,12 @@ and "clean up the prior copy" are contradictory instructions once that rename ha
|
|
|
243
244
|
outside `P`; do not copy slice worktrees or any other part of `S`.
|
|
244
245
|
3. **Verify.** Build source and destination inventories exactly as the completed archive does — every
|
|
245
246
|
entry's relative path, type and mode, a SHA-256 for each regular file, a link target for each symlink,
|
|
246
|
-
sorted lexically — and require exact equality
|
|
247
|
+
sorted lexically — and require exact equality, **excluding the plane-root `factory.lock` only**. That
|
|
248
|
+
one entry is session liveness rather than run state and is the only thing in the plane designed to
|
|
249
|
+
change on a timer, so comparing it fails whenever a heartbeat lands between reading the source and
|
|
250
|
+
reading the copy. The exclusion is that exact path and nothing else: a `factory.lock` anywhere below
|
|
251
|
+
the plane root is run state and must match. Qualified status excludes the same single path for the same
|
|
252
|
+
reason. An unverified staging tree is never published.
|
|
247
253
|
4. **Commit.** With no snapshot at the canonical path, rename `.staging-$R` onto it; that rename is the
|
|
248
254
|
commit point. With one present, first rename the canonical snapshot to `.prior-$R`, then rename
|
|
249
255
|
`.staging-$R` onto the canonical path; that second rename is the commit point. If the first rename
|
|
@@ -295,8 +301,12 @@ fresh run; an existing run follows these rules solely because its manifest alrea
|
|
|
295
301
|
every acceptance criterion maps to a slice, and same-wave slices are file-disjoint.
|
|
296
302
|
- **Gate 3 (pre-PR)**: approve only on a GO or GO-WITH-NITS validator verdict with `review_ready`
|
|
297
303
|
observed evidence for the integrated branch. A NO-GO is a NO-GO.
|
|
298
|
-
- **Never auto-merge.**
|
|
299
|
-
may perform.
|
|
304
|
+
- **Never auto-merge.** Recording the pull request is the last externally publishing side effect an
|
|
305
|
+
autonomous run may perform. Whether that PR is a draft is the repository's `pr_draft` choice and is
|
|
306
|
+
independent of the run mode: autonomous does not imply draft, and `pr_draft: false` is a supported
|
|
307
|
+
configuration that publishes ready-for-review in every mode. The constraint here is about stopping
|
|
308
|
+
after publication, not about draft-ness.
|
|
309
|
+
After it is recorded, the mandatory local completed handoff in Step 7 still follows and
|
|
300
310
|
is required in every mode: terminalize, fetch the permitted local refs, archive and verify the control
|
|
301
311
|
plane, and remove only the guarded sandbox. Autonomous mode never merges an external PR or performs
|
|
302
312
|
unrelated work after PR recording.
|
|
@@ -562,7 +572,7 @@ After derivation, `O` is the physically resolved operator checkout. During boots
|
|
|
562
572
|
execution, do not switch, reset, clean, stash, create a branch or worktree, write Git configuration, or
|
|
563
573
|
initialize factory state directly in `O`. The only operator-checkout operations before the completed
|
|
564
574
|
handoff are reads and the Step 6 forge command. The explicit Step 7 exclusion applies only after the
|
|
565
|
-
|
|
575
|
+
pull request is recorded: its guarded local-ref fetch, archive, verification, and deterministic sandbox
|
|
566
576
|
removal remain the sole completed-handoff exception to bootstrap/refusal state preservation.
|
|
567
577
|
|
|
568
578
|
### Resume or collision
|
|
@@ -1816,10 +1826,10 @@ command; never move it earlier or present stale evidence.
|
|
|
1816
1826
|
The compatibility transition name is `factory gate <run-id> pre_pr pending`; the runnable form is the
|
|
1817
1827
|
repository-qualified command above.
|
|
1818
1828
|
|
|
1819
|
-
The draft publication signature is `gh pr create --draft --base "<pr_base>" --head "<branch>" --title "<title>" --body-file "<body-file>"`.
|
|
1820
|
-
The ready-for-review publication signature is `gh pr create --base "<pr_base>" --head "<branch>" --title "<title>" --body-file "<body-file>"`.
|
|
1829
|
+
The `PR_DRAFT=true` draft publication signature is `gh pr create --draft --base "<pr_base>" --head "<branch>" --title "<title>" --body-file "<body-file>"`.
|
|
1830
|
+
The `PR_DRAFT=false` ready-for-review publication signature is `gh pr create --base "<pr_base>" --head "<branch>" --title "<title>" --body-file "<body-file>"`.
|
|
1821
1831
|
|
|
1822
|
-
## Step 6 — Draft PR
|
|
1832
|
+
## Step 6 — Draft PR (ready-for-review when `pr_draft` is false)
|
|
1823
1833
|
|
|
1824
1834
|
Immediately before any publication effect, read the delivery intent from the selected run repository,
|
|
1825
1835
|
then, for a sandbox-selected run, repeat the operator exact-ref-absent and sandbox
|
|
@@ -1951,12 +1961,13 @@ whatever mapping the repository documents, and update the tracker only through *
|
|
|
1951
1961
|
|
|
1952
1962
|
## Step 7 — Summary and completed sandbox handoff
|
|
1953
1963
|
|
|
1954
|
-
After
|
|
1964
|
+
After the pull request is recorded -- draft or ready for review, as `pr_draft`
|
|
1965
|
+
selected -- `interactive`, `headless`, and `autonomous` modes all enter this same mandatory
|
|
1955
1966
|
local completed handoff. In autonomous mode this is the sole narrow exception to the external-side-effect
|
|
1956
1967
|
stop: perform only the terminalize, local-ref fetch, archive, verification, and guarded sandbox-removal
|
|
1957
1968
|
sequence below, with no external PR merge or unrelated work after PR recording.
|
|
1958
1969
|
|
|
1959
|
-
After `factory pr` records the
|
|
1970
|
+
After `factory pr` records the pull request, stop the heartbeat loop and wait for any heartbeat call already
|
|
1960
1971
|
in flight to return. Before terminalization or any filesystem or Git side effect, require that the loop
|
|
1961
1972
|
is no longer active and no dispatched agent call remains in flight, directly read and validate exactly
|
|
1962
1973
|
`RUN_MANIFEST`, and require no step with status `running` and no slice with status `running` or `review`.
|
|
@@ -1975,6 +1986,12 @@ Terminalize before any housekeeping, through the repository selected in Step 0:
|
|
|
1975
1986
|
factory terminal "$R" completed --reason "draft-pr-recorded" --repo "$RUN_REPO"
|
|
1976
1987
|
```
|
|
1977
1988
|
|
|
1989
|
+
`draft-pr-recorded` is a fixed protocol token meaning this run's pull request was created and recorded.
|
|
1990
|
+
It does not assert the PR is a draft: a `pr_draft: false` run records a ready-for-review PR and
|
|
1991
|
+
terminalizes with this same reason. Never vary it by mode or by `pr_draft`; downstream consumers match
|
|
1992
|
+
it exactly.
|
|
1993
|
+
|
|
1994
|
+
|
|
1978
1995
|
Do not replay or retry any handoff phase. A later invocation that finds a completed sandbox reports its
|
|
1979
1996
|
path for manual recovery and leaves it intact. For the same reason, do not invent durable phase state or
|
|
1980
1997
|
infer that an interrupted effect is safe to repeat.
|
|
@@ -2118,7 +2135,8 @@ Never re-do a side effect the manifest shows already done — ticket creation, p
|
|
|
2118
2135
|
Qualified status reports the run's `max_retries`, so the budget a run is actually bounded by is
|
|
2119
2136
|
observable rather than assumed: a forwarded `--max-retries` that never reached the manifest is visible
|
|
2120
2137
|
as a different number instead of silently running at the default.
|
|
2121
|
-
- **
|
|
2138
|
+
- **Publish a PR and stop.** Never merge, force-push, or close tickets. Humans merge. Draft or
|
|
2139
|
+
ready-for-review is `pr_draft`'s decision, not this rule's.
|
|
2122
2140
|
- **Scope discipline and no fabrication.** Flag out-of-scope work at the next gate. Never invent paths,
|
|
2123
2141
|
keys, versions, or test passes — if the evidence is thin, say so and ask.
|
|
2124
2142
|
- **A repository may lock its own scope, and a lock is not a defect.** A check whose assertion *is* a
|
package/bin/factory.js
CHANGED
|
@@ -23,7 +23,7 @@ import { resolveSpawnExecutable } from "../core/executable.js";
|
|
|
23
23
|
import { dispatchInitPublication } from "./init-publication.js";
|
|
24
24
|
import { CONTROL_PLANE, SCHEMA_VERSION, GATE_NAMES, GATE_STATUSES, MODES, SLICE_STATUSES, STEP_STATUSES, TERMINAL_STATUSES, repositoryRelativePath, validateRun } from "../state/schema.js";
|
|
25
25
|
import {
|
|
26
|
-
claimSessionLock, inspectSessionLock, refreshSessionLock, releaseSessionLock, SessionLockHeldError,
|
|
26
|
+
claimSessionLock, inspectSessionLock, refreshSessionLock, releaseSessionLock, SESSION_LOCK_FILE, SessionLockHeldError,
|
|
27
27
|
} from "../state/session-lock.js";
|
|
28
28
|
|
|
29
29
|
export const COMMANDS = Object.freeze({
|
|
@@ -175,6 +175,15 @@ function briefDigestFor(decision, state, runDir) {
|
|
|
175
175
|
function planeInventory(root) {
|
|
176
176
|
const entries = [];
|
|
177
177
|
const record = (rel, full) => {
|
|
178
|
+
// The session lock is liveness, not run state, and it is the one entry in the plane designed to change
|
|
179
|
+
// on a timer. Comparing it made every snapshot invalid within one heartbeat: a live park published a
|
|
180
|
+
// complete, byte-correct plane and `status` reported `park_snapshot: null` eleven seconds later,
|
|
181
|
+
// because the copy held `heartbeat_at` 23:28:25 and the plane had moved to 23:28:36. The whole point
|
|
182
|
+
// of the field was to answer "did the driver publish it", and it answered "no" for a snapshot that was
|
|
183
|
+
// there -- the same disagree-with-your-own-description defect the 0.8.3 work existed to remove,
|
|
184
|
+
// reintroduced by the check built to remove it. Every existing test publishes and reads back with no
|
|
185
|
+
// heartbeat in between, which is why three review rounds and a real park all missed it.
|
|
186
|
+
if (rel === SESSION_LOCK_FILE) return;
|
|
178
187
|
const stat = lstatSync(full);
|
|
179
188
|
const mode = (stat.mode & 0o7777).toString(8);
|
|
180
189
|
if (stat.isSymbolicLink()) entries.push(`${rel} l ${mode} ${readlinkSync(full)}`);
|