feature-factory 0.10.0 → 0.10.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.
- package/README.md +12 -0
- package/WORKFLOW.md +107 -40
- package/bin/factory.js +245 -42
- package/bin/restore.js +27 -7
- package/bin/snapshot.js +173 -0
- package/core/atomic-write.js +11 -7
- package/core/contracts.js +48 -7
- package/core/run-lock.js +26 -6
- package/core/write-core.js +17 -4
- package/observe/repair-reverification.js +3 -3
- package/package.json +1 -1
- package/state/retry-grant-transaction.js +194 -0
- package/state/review-archive.js +36 -22
- package/state/schema.js +85 -4
- package/state/session-lock.js +3 -3
- package/state/transition.js +3 -1
package/README.md
CHANGED
|
@@ -258,6 +258,18 @@ own the same path. Duplicate, target-already-owned, malformed, privileged, repla
|
|
|
258
258
|
requests refuse atomically. Resume never amends or reseeds. A merge continues to refuse every unamended
|
|
259
259
|
or privileged changed path.
|
|
260
260
|
|
|
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 durably fences the prior canonical snapshot before committing its
|
|
269
|
+
manifest, then removes it only after the commit is proved; restore and parked mutations refuse any
|
|
270
|
+
interrupted fence. Run `factory snapshot <run-id> --repo <operator>` and requalify it before the separate
|
|
271
|
+
explicit resume. Restored blocked slices whose physical refs were intentionally cleared do not qualify.
|
|
272
|
+
|
|
261
273
|
`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.
|
|
262
274
|
Effective push-target capture and comparison are active through the package-owned `factory effective-push` command; they are not deferred to configured `publish`.
|
|
263
275
|
The recorded `publishing_identity` is read from `status` exactly as reported, without trimming,
|
package/WORKFLOW.md
CHANGED
|
@@ -209,9 +209,41 @@ sequence with `UNKNOWN_OUTCOME_REASON`. Never repeat successful siblings.
|
|
|
209
209
|
A budgeted attempt advances only after a complete specialist response reaches the ordinary workflow and
|
|
210
210
|
that response is rejected on its merits or violates the specialist's required output contract. A complete
|
|
211
211
|
unbudgeted result returns to its ordinary workflow without creating an attempt. Infrastructure recovery is
|
|
212
|
-
the same attempt, not another use of `max_retries`.
|
|
213
|
-
|
|
214
|
-
|
|
212
|
+
the same attempt, not another use of `max_retries`. For a slice, an output-contract violation may advance
|
|
213
|
+
only when canonical evidence and its matching REJECT review were recorded; if the malformed output prevents
|
|
214
|
+
either record, park top-level `needs-human` through the common procedure instead of fabricating authority for
|
|
215
|
+
N+1. This rule is instruction rather than CLI enforcement: the host owns specialist invocation errors, while
|
|
216
|
+
the CLI continues to enforce every durable attempt transition the driver actually records.
|
|
217
|
+
|
|
218
|
+
### Operator-authorized retry extension
|
|
219
|
+
|
|
220
|
+
A slice that reached its effective retry limit remains terminal until an operator explicitly grants exactly
|
|
221
|
+
one more attempt. Never edit `run.json`. Keep the top-level run in its existing parked state, claim and verify its
|
|
222
|
+
fresh session lock, record a concrete reason why N+1 is now bounded and materially different, and choose one
|
|
223
|
+
scope deliberately:
|
|
224
|
+
|
|
225
|
+
```sh
|
|
226
|
+
factory grant-retry "$R" "$SLICE_ID" --scope slice --reason "$EXTENSION_REASON" --session "$SESSION_ID" --repo "$RUN_REPO"
|
|
227
|
+
factory grant-retry "$R" "$SLICE_ID" --scope all --reason "$EXTENSION_REASON" --session "$SESSION_ID" --repo "$RUN_REPO"
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
`slice` raises only that slice's additive allowance. `all` raises the run-wide default, including every
|
|
231
|
+
pending later wave, but still reopens only `SLICE_ID`; it refuses while another slice is blocked or an
|
|
232
|
+
exhausted post-merge repair exists. Both scopes require `blocked@N` exactly at the current effective limit;
|
|
233
|
+
a matching REJECT, evidence, immutable base and live clean branch head; the exact fresh lock owner; a
|
|
234
|
+
complete current park snapshot; and immutable attempt-N review and evidence archives. A legacy run missing
|
|
235
|
+
an archive gets a preparation-only refusal: publish the changed plane and invoke the grant again. They
|
|
236
|
+
append the durable authorization, preserve worktree, branch and `base_ref`, clear only the live attempt-bound
|
|
237
|
+
refs, and record `running@(N+1)` while top-level status and `terminal_result` remain parked.
|
|
238
|
+
|
|
239
|
+
The grant durably fences the old canonical snapshot before committing `run.json`, then removes it only
|
|
240
|
+
when the manifest commit is proved. Restore and parked mutations refuse an interrupted fence; `factory
|
|
241
|
+
snapshot` reconciles exact pre-commit or post-commit state under the run lock. Do not dispatch or resume yet. Republish the updated live plane, then require qualified status
|
|
242
|
+
to report the refreshed `park_snapshot`, unchanged owner, the chosen new effective limit, and only the named
|
|
243
|
+
slice at `running@(N+1)`. Only then run the ordinary explicit
|
|
244
|
+
`factory resume "$R" --session "$SESSION_ID" --repo "$RUN_REPO"` and dispatch that attempt. Resume refreshes
|
|
245
|
+
the staged workflow before its final snapshot check. A grant never invokes a specialist, unlocks, resumes,
|
|
246
|
+
approves, merges, or publishes.
|
|
215
247
|
|
|
216
248
|
## The chain
|
|
217
249
|
|
|
@@ -379,6 +411,12 @@ sandbox: the completed handoff is the only thing that archives it, and that hand
|
|
|
379
411
|
`completed`. So anything that removed the sandbox destroyed the manifest, the approved gates, the ratified
|
|
380
412
|
plan and every review verdict, leaving the run neither resumable nor reconstructable.
|
|
381
413
|
|
|
414
|
+
`factory snapshot "$R" --repo "$O" --json` performs this publication and is the supported way to do it.
|
|
415
|
+
The steps below remain the definition of what it does; a driver may run the command instead of carrying
|
|
416
|
+
them out itself, and a supervisor that parked a run out-of-band **must** run it, because `factory
|
|
417
|
+
terminal` alone completes step 1 of the park and leaves no recovery evidence. It refuses a run that is
|
|
418
|
+
not parked, so it cannot record a live plane as a snapshot of a moment no resume can return to.
|
|
419
|
+
|
|
382
420
|
Publish the live plane `P` to `$O/.factory/.parked/$R`. Inspect `$O/.factory` and `$O/.factory/.parked` with
|
|
383
421
|
non-following metadata reads, creating each missing parent one directory at a time and requiring any present
|
|
384
422
|
one to be a real directory rather than a symbolic link. Never write through a symlinked parent, and never
|
|
@@ -396,12 +434,11 @@ and "clean up the prior copy" are contradictory instructions once that rename ha
|
|
|
396
434
|
outside `P`; do not copy slice worktrees or any other part of `S`.
|
|
397
435
|
3. **Verify.** Build source and destination inventories exactly as the completed archive does — every
|
|
398
436
|
entry's relative path, type and mode, a SHA-256 for each regular file, a link target for each symlink,
|
|
399
|
-
sorted lexically — and require exact equality,
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
the
|
|
404
|
-
reason. An unverified staging tree is never published.
|
|
437
|
+
sorted lexically — and require exact equality, excluding only plane-root `factory.lock` and
|
|
438
|
+
`run-json.lock`. The first is session liveness and can change on a timer; the second is held by the
|
|
439
|
+
snapshot command to serialize publication with state transitions. The exclusions are those exact root
|
|
440
|
+
paths and nothing else: either name below the plane root is run state and must match. Qualified status
|
|
441
|
+
applies the same exact exclusions at a transition boundary. An unverified staging tree is never published.
|
|
405
442
|
4. **Commit.** With no snapshot at the canonical path, rename `.staging-$R` onto it; that rename is the
|
|
406
443
|
commit point. With one present, first rename the canonical snapshot to `.prior-$R`, then rename
|
|
407
444
|
`.staging-$R` onto the canonical path; that second rename is the commit point. If the first rename
|
|
@@ -1413,11 +1450,16 @@ For a fresh pending slice, set the exact names, require both `refs/heads/$SLICE_
|
|
|
1413
1450
|
`SLICE_WORKTREE` path to be absent, and create the worktree from the current feature branch before
|
|
1414
1451
|
activation:
|
|
1415
1452
|
|
|
1453
|
+
Before creating a pending slice worktree, reload its exact manifest row. Bind `ACTIVATION_START` to
|
|
1454
|
+
`FEATURE_BRANCH` when `base_ref` is null. When a restored retry-extension row preserves non-null `base_ref`,
|
|
1455
|
+
require its latest audit to name the same base and start the replacement slice branch at that exact historical
|
|
1456
|
+
base. This recreates the original retry branch without importing later sibling changes into its owned diff.
|
|
1457
|
+
|
|
1416
1458
|
```sh
|
|
1417
1459
|
SLICE_BRANCH="factory/$R/$SLICE_ID"
|
|
1418
1460
|
SLICE_WORKTREE="$SLICE_ROOT/$SLICE_ID"
|
|
1419
1461
|
CHECKED_OUT_FEATURE_BRANCH="$(git -C "$INTEGRATION_WORKTREE" symbolic-ref --quiet --short HEAD)"
|
|
1420
|
-
git -C "$RUN_REPO" worktree add -b "$SLICE_BRANCH" "$SLICE_WORKTREE" "$
|
|
1462
|
+
git -C "$RUN_REPO" worktree add -b "$SLICE_BRANCH" "$SLICE_WORKTREE" "$ACTIVATION_START"
|
|
1421
1463
|
$ factory slice "$R" "$SLICE_ID" running --worktree "$SLICE_WORKTREE" --branch "$SLICE_BRANCH" --repo "$RUN_REPO"
|
|
1422
1464
|
```
|
|
1423
1465
|
|
|
@@ -1431,6 +1473,8 @@ Step 0. Require `run_id === R`, select exactly one `slices` row with `id === SLI
|
|
|
1431
1473
|
|
|
1432
1474
|
```text
|
|
1433
1475
|
RECORDED_SLICE = parsedRun.slices row whose id equals SLICE_ID
|
|
1476
|
+
MAX_RETRIES = parsedRun.max_retries
|
|
1477
|
+
SLICE_RETRY_LIMIT = MAX_RETRIES + (RECORDED_SLICE.extra_attempts when present, otherwise 0)
|
|
1434
1478
|
SLICE_WORKTREE = RECORDED_SLICE.worktree
|
|
1435
1479
|
SLICE_BRANCH = RECORDED_SLICE.branch
|
|
1436
1480
|
SLICE_BASE_REF = RECORDED_SLICE.base_ref
|
|
@@ -1444,13 +1488,15 @@ for it. A driver that assumes "this is the first try" observes as attempt 1 whil
|
|
|
1444
1488
|
merge then refuses that evidence — `evidence '…' is for attempt 1, slice is at attempt 2` — after the build
|
|
1445
1489
|
and the review have already been spent. It names the report and the `--attempt` argument below.
|
|
1446
1490
|
|
|
1447
|
-
Require
|
|
1448
|
-
a
|
|
1449
|
-
|
|
1450
|
-
`
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1491
|
+
Require every bound value to be non-null, `MAX_RETRIES`, `SLICE_RETRY_LIMIT`, and `SLICE_ATTEMPT` to be positive integers,
|
|
1492
|
+
`SLICE_BASE_REF` to be a 40-character commit SHA, `SLICE_BRANCH` to equal `factory/R/<slice-id>`, and the
|
|
1493
|
+
physical `SLICE_WORKTREE` to equal `SLICE_ROOT/<slice-id>`. Require `git -C "$RUN_REPO" worktree list
|
|
1494
|
+
--porcelain` to associate that physical path with that exact branch. Before dispatch or observation require
|
|
1495
|
+
status `running`. A `review` row is a completed decision checkpoint: on resume consume its recorded review
|
|
1496
|
+
through step 4, never re-observe it. A pending slice requires path and branch ref to remain absent. Its
|
|
1497
|
+
base is absent unless a restored retry-extension audit preserves that immutable base; any unrecorded path
|
|
1498
|
+
or branch ref is a collision. Refuse every mismatch instead of repairing, deleting, or
|
|
1499
|
+
reassociating it. A merged slice is never dispatched again.
|
|
1454
1500
|
|
|
1455
1501
|
For a non-empty `SLICE_TEST_PLAN`, select one complete entry and bind `SLICE_TEST_COMMAND` by copying
|
|
1456
1502
|
that persisted string verbatim. Never shorten, append to, normalize, or source it from the mutable
|
|
@@ -1504,31 +1550,32 @@ Per slice:
|
|
|
1504
1550
|
is no repair available at this step.** The slice is already activated, so `base_ref` is fixed; the suite
|
|
1505
1551
|
runs in `SLICE_WORKTREE`, so a commit on the integration branch is invisible to the re-observation; and
|
|
1506
1552
|
bringing that commit into the slice would put an out-of-lane test path in the observed diff, which the
|
|
1507
|
-
merge refuses.
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1553
|
+
merge refuses. This is a ratified-plan conflict, not a merit REJECT: do not mark the slice `blocked`
|
|
1554
|
+
without canonical evidence and a matching max-attempt review. Stop dispatching its dependents and park
|
|
1555
|
+
top-level `needs-human` through the common procedure with the diagnosis below. Retain merged siblings on
|
|
1556
|
+
the integration branch and the whole sandbox for operator replanning. The parked run is **surfaced, not
|
|
1557
|
+
published**: Gate 3 refuses the approval that authorizes publication unless every slice is `merged`, so
|
|
1558
|
+
an operator decides what to do with the work rather than a PR appearing for a plan that did not finish.
|
|
1512
1559
|
|
|
1513
1560
|
**Never narrow the ratified command to get past this.** `factory observe` compares the raw supplied
|
|
1514
1561
|
slice command with the persisted ratified entries before tokenization or execution and refuses a
|
|
1515
|
-
shortened, appended, or normalized command without writing evidence. A narrowed command is a false green wearing evidence's clothes;
|
|
1562
|
+
shortened, appended, or normalized command without writing evidence. A narrowed command is a false green wearing evidence's clothes; parking for replanning is the honest outcome when the verbatim command fails.
|
|
1516
1563
|
|
|
1517
1564
|
If the same incompatibility instead first appears in the **integrated** suite, this step is not involved
|
|
1518
1565
|
at all — Step 5's NO-GO repair owns it, on the branch where that suite actually runs.
|
|
1519
1566
|
|
|
1520
|
-
When you
|
|
1521
|
-
`--reason`: which slice owns the test, which assertion cannot hold, and what would
|
|
1522
|
-
reason naming only "tests failed" makes the operator repeat the whole investigation,
|
|
1523
|
-
difference between their fix being one commit and being an afternoon.
|
|
1567
|
+
When you park this plan conflict, record the **diagnosis** and not just the failure in the park
|
|
1568
|
+
terminal transition's `--reason`: which slice owns the test, which assertion cannot hold, and what would
|
|
1569
|
+
make it hold. A reason naming only "tests failed" makes the operator repeat the whole investigation,
|
|
1570
|
+
which is the difference between their fix being one commit and being an afternoon.
|
|
1524
1571
|
|
|
1525
1572
|
An out-of-lane **production** change is a different thing entirely and follows **Ownership disclosure**
|
|
1526
1573
|
below, where the reviewer decides whether the plan or the change is wrong.
|
|
1527
1574
|
4. **Review** — `work-reviewer` with subject `<slice-id>`, the observed evidence, the slice spec, and
|
|
1528
1575
|
the brief. Record both refs — the merge requires each:
|
|
1529
1576
|
```sh
|
|
1530
|
-
$ factory slice "$R" "$SLICE_ID" review --
|
|
1531
|
-
--review-ref "reviews/$SLICE_ID.json" --repo "$RUN_REPO"
|
|
1577
|
+
$ factory slice "$R" "$SLICE_ID" review --attempts "$SLICE_ATTEMPT" \
|
|
1578
|
+
--evidence-ref "evidence/$SLICE_ID.json" --review-ref "reviews/$SLICE_ID.json" --repo "$RUN_REPO"
|
|
1532
1579
|
```
|
|
1533
1580
|
- On REJECT, before spending an attempt, identify the cause of the remaining failures. If the fix would
|
|
1534
1581
|
violate an approved story or brief constraint, or repeated findings trace to the same unresolved
|
|
@@ -1538,8 +1585,26 @@ Per slice:
|
|
|
1538
1585
|
If no such target can be identified, park through the existing parked-stop procedure for replanning
|
|
1539
1586
|
or operator clarification; preserve the work and do not silently change approved acceptance criteria.
|
|
1540
1587
|
Unchanged finding counts alone are not a stall: progress can occur within a category that remains open.
|
|
1541
|
-
|
|
1542
|
-
`
|
|
1588
|
+
Only a complete merit REJECT spends an attempt; infrastructure recovery and resume preserve
|
|
1589
|
+
`SLICE_ATTEMPT` under the common rules above. Reload `RUN_MANIFEST`, require the recorded review to name
|
|
1590
|
+
this slice and `SLICE_ATTEMPT` with exact verdict `REJECT`, and require the row still to be `review`.
|
|
1591
|
+
If `SLICE_ATTEMPT >= SLICE_RETRY_LIMIT`, record the terminal slice state and stop dispatching its
|
|
1592
|
+
dependents without creating another attempt:
|
|
1593
|
+
```sh
|
|
1594
|
+
$ factory slice "$R" "$SLICE_ID" blocked --attempts "$SLICE_ATTEMPT" --repo "$RUN_REPO"
|
|
1595
|
+
```
|
|
1596
|
+
Then enter the common parked-stop procedure with exact reason `blocked-after-retries`; do not
|
|
1597
|
+
terminalize `partial`. The parked snapshot and retained lock are what make a later audited grant
|
|
1598
|
+
reachable without weakening the terminal slice transition.
|
|
1599
|
+
Otherwise bind `NEXT_SLICE_ATTEMPT = SLICE_ATTEMPT + 1` and, before redispatch, record:
|
|
1600
|
+
```sh
|
|
1601
|
+
$ factory slice "$R" "$SLICE_ID" running --attempts "$NEXT_SLICE_ATTEMPT" --repo "$RUN_REPO"
|
|
1602
|
+
```
|
|
1603
|
+
This retry-opening command takes no worktree, branch, evidence, or review flag. Reload the row and
|
|
1604
|
+
require status `running`, attempt `NEXT_SLICE_ATTEMPT`, unchanged worktree, branch, and exact
|
|
1605
|
+
`SLICE_BASE_REF`, and null evidence and review refs. The base is the immutable original branch point,
|
|
1606
|
+
not merely any ancestor and not the integration head after sibling merges. Then set `SLICE_ATTEMPT` to
|
|
1607
|
+
the recorded new value, route the bounded fixes back to that builder, and re-observe.
|
|
1543
1608
|
5. **Merge (you, serially)** — on APPROVE, merge the slice branch into the feature branch one at a
|
|
1544
1609
|
time. Builds are concurrent; merges are single-writer, which is what makes the parallelism safe.
|
|
1545
1610
|
```sh
|
|
@@ -1857,10 +1922,12 @@ content on those paths matches what was reviewed, so unreviewed content inside *
|
|
|
1857
1922
|
while movement around it is not. What guards the branch as a whole is the integration pass: the
|
|
1858
1923
|
validator judges the whole diff and Gate 3 will not approve unless the head it judged is still the head.
|
|
1859
1924
|
|
|
1860
|
-
Advance waves until all slices are `merged`, or a slice is `blocked`.
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
Use terminal needs-human only to park a running envelope; use explicit factory resume after the cause is fixed.
|
|
1925
|
+
Advance waves until all slices are `merged`, or a slice is `blocked`. A blocked slice stops the wave and
|
|
1926
|
+
enters the common parked-stop procedure as top-level `needs-human`; retry exhaustion never terminalizes the
|
|
1927
|
+
run as `partial`. Use explicit factory resume only after the cause is fixed or a qualified retry grant was
|
|
1928
|
+
recorded and the updated plane was published. Use terminal needs-human only to park a running envelope; use explicit factory resume after the cause is fixed.
|
|
1929
|
+
A `partial` run is **surfaced, not published** when some other checked terminal cause creates one; retry
|
|
1930
|
+
exhaustion uses the parked path above instead.
|
|
1864
1931
|
A top-level needs-human sandbox stays retained while parked and continues only after explicit factory resume.
|
|
1865
1932
|
A `blocked` or `partial` sandbox run retains `RUN_REPO`; stale nonterminal locks retain it
|
|
1866
1933
|
too. Nothing removes any of those sandboxes automatically. Legacy runs
|
|
@@ -2380,11 +2447,11 @@ Never re-do a side effect the manifest shows already done — ticket creation, p
|
|
|
2380
2447
|
Specialists are read-only toward them and builders write code only inside the worktree they receive.
|
|
2381
2448
|
- **Never hand-write `run.json`.** If a `factory` command refuses a transition, the refusal is the
|
|
2382
2449
|
answer; do not work around it by editing state.
|
|
2383
|
-
- **Bounded loops.**
|
|
2384
|
-
`
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2450
|
+
- **Bounded loops.** Each slice is bounded by `max_retries + extra_attempts`; each step uses
|
|
2451
|
+
`max_retries`. On slice exhaustion mark it `blocked` and park top-level needs-human; do not terminalize
|
|
2452
|
+
`partial` solely for retry exhaustion. Explicit resume may repark if the external cause remains unfixed.
|
|
2453
|
+
Qualified status reports the run's `max_retries` and each slice's effective `retry_limit`, so an
|
|
2454
|
+
extended budget is observed rather than assumed.
|
|
2388
2455
|
- **Publish a PR and stop.** Never merge, force-push, or close tickets. Humans merge. Draft or
|
|
2389
2456
|
ready-for-review is `pr_draft`'s decision, not this rule's.
|
|
2390
2457
|
- **Scope discipline and no fabrication.** Flag out-of-scope work at the next gate. Never invent paths,
|