wowbagger 0.1.0-alpha.13 → 0.1.0-alpha.14

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.
@@ -334,57 +334,186 @@ The plain statement: **a recorded private write in one worktree blocks
334
334
  mutations targeting that item, not unrelated item mutations in sibling
335
335
  worktrees.** Own uncommitted work and out-of-protocol revisions remain global
336
336
  safety barriers. This is item-scoped availability, not exclusive dispatch.
337
+ `create` reads that same reconciliation with one extra barrier, because it is
338
+ the one mutation whose output depends on an identity no caller supplied.
339
+
340
+ **Superseded: create no longer stays journal-silent.** Through alpha.13 this
341
+ contract decided that create records nothing, and three reasons held that
342
+ decision: create already protects its own instant, because publication is
343
+ atomic, refuses to clobber an existing path, and verifies the published bytes
344
+ exactly; a journaled create would serialize every worktree on the
345
+ highest-volume mutation; and the remaining exposure window was judged narrow
346
+ because it closed at the item's first journal-visible mutation.
347
+
348
+ Item #181 overturns that decision. The first reason answers the wrong
349
+ question. Create's atomic instant protects the path it publishes to, and
350
+ nothing protected the `number` it derives. A schema-version-2 create takes
351
+ `1 + max(existing numbers)` from the items its own checkout holds, so two
352
+ worktrees that have not integrated each other's commits derive the same number
353
+ and both publish. They need not overlap in time: a create today and a create
354
+ tomorrow collide as reliably as two simultaneous ones, because the loser is a
355
+ stale checkout rather than a lost race, and a wider mutex cannot fix a
356
+ sequential failure. Only durable evidence can. `number` is immutable and
357
+ `patch` correctly rejects it, so an undetected collision surfaces at
358
+ integration as a global `duplicate-number` validation failure that stops the
359
+ whole ledger.
360
+
361
+ **The fixed ordering.** On a provisioned ledger a create is a legacy mutation
362
+ like any other, and the shared namespace lock is held across every step:
363
+
364
+ 1. Acquire the shared namespace lock.
365
+ 2. Reconcile the repository state and apply the allocation fence below.
366
+ 3. Load this worktree's ledger.
367
+ 4. Acquire the item, relation, and number-index lock closure.
368
+ 5. Reload and validate the ledger under those locks.
369
+ 6. Derive `1 + max(existing numbers)`.
370
+ 7. Serialize the candidate item.
371
+ 8. Validate the complete candidate ledger.
372
+ 9. Reserve journal capacity, then append the create intent.
373
+ 10. Publish with atomic no-clobber semantics.
374
+ 11. Verify the final path holds the exact candidate bytes.
375
+ 12. Append the committed or aborted terminal.
376
+
377
+ A create appends its `legacy-mutation-intent` with `command: "create-v1"`
378
+ before any byte reaches the item path, and it appends that intent only after
379
+ step 8, so a refusal the command would have returned anyway records no
380
+ attempt. The intent carries `expected_revision: null`, which is valid only for
381
+ `create-v1`; every other command still requires a string revision. The
382
+ committed terminal is `legacy-mutation` with `command: "create-v1"`. The
383
+ create abort carries `command: "create-v1"` and `observed_revision: null`,
384
+ while an abort for any other command remains valid without `command` and still
385
+ requires a string revision. No entry records the assigned number: the
386
+ candidate revision binds the complete item bytes, and the ledger stays the one
387
+ number authority.
388
+
389
+ **The allocation fence.** Create reconciles with its own item as the target,
390
+ exactly like every other mutation, and one extra barrier reads the result.
391
+ Every global finding blocks create because it blocks every write:
392
+ `git-finalization-required` for this worktree's own uncommitted authorized
393
+ bytes, `unauthorized-revision` for out-of-protocol bytes, and an unresolved
394
+ `legacy-mutation-outcome-unknown`. On top of those, any coordinated item this
395
+ checkout does not hold blocks `create`, because the journal records a
396
+ committed terminal for an item whose number this working ledger cannot read,
397
+ so the next number derived here may be one a sibling already published. A
398
+ stale revision of an item this checkout holds does not block `create`: a
399
+ number is immutable, so the local maximum is already correct, and that finding
400
+ keeps its ordinary target scope.
401
+
402
+ **The old exposure window is closed on a provisioned ledger.** The journal used
403
+ to learn of an item only at its first `transition` or `patch`, so an
404
+ out-of-protocol overwrite before that mutation went unreported. A journaled
405
+ create records an authorized revision at the item's birth, so the ordinary
406
+ surfaces cover the item from then on: an out-of-protocol overwrite reports
407
+ `unauthorized-revision` and the next mutation refuses. An unprovisioned or
408
+ non-Git ledger keeps only its local number-index lock and its atomic
409
+ no-clobber publication; nothing there coordinates across checkouts, and nothing
410
+ here defends against an actor that bypasses this tool.
411
+
412
+ **The refusal.** A fenced create refuses before it allocates a number and
413
+ before it writes a byte:
414
+
415
+ ```text
416
+ exit: 6
417
+ ok: false
418
+ namespace: "ledger-mutation"
419
+ command: "create-v1"
420
+ contract_version: 1
421
+ state: "unchanged"
422
+ error.code: "claim-store-unavailable"
423
+ error.details.reason: "publication-reconciliation-required"
424
+ ```
337
425
 
338
- **Decided: create stays journal-silent.** The asymmetry is intended. Three
339
- reasons hold it:
340
-
341
- 1. Create already protects its own instant. Publication is atomic, it refuses
342
- to clobber an existing path, and it verifies the published bytes exactly
343
- after the write. A journal entry adds no protection to that instant.
344
- 2. A journaled create would serialize every worktree on the highest-volume
345
- mutation. The field reports name create as the most frequent mutation and as
346
- the most frequent victim of a block. Making create a blocker as well as a
347
- victim multiplies a cost that consumers already report.
348
- 3. The remaining exposure window is real but narrow, and it closes at the
349
- item's first journal-visible mutation.
350
-
351
- **The exposure window, stated honestly.** The journal does not know a created
352
- item until that item's first journal-visible mutation, which is a `transition`
353
- or a `patch`. Until then an out-of-protocol overwrite of the item's bytes is
354
- not detected. A commit alone does not close the window, because reconciliation
355
- compares only the revisions the journal expects, and the journal expects none
356
- for that item. From the first `transition` or `patch`, the ordinary surfaces
357
- cover the item: with the authorized revision committed, an out-of-protocol
358
- overwrite of the working-tree bytes reports `unauthorized-revision` and the
359
- next mutation refuses. Inside the window, only an actor that bypasses this tool
360
- can overwrite the item, and this protocol does not defend against that actor.
361
- It is merge-coordinated, not exclusive.
362
-
363
- **Warning: the same window lets more than one worktree commit one item number,
364
- and nothing repairs it** (items #181 and #182). Because create is
365
- journal-silent, nothing coordinates the number a create derives. Any two
366
- worktrees whose checkouts have not been integrated derive the next schema-v2
367
- `number` from the base each can see, and both derive the same one. The two
368
- creates need not overlap in time: a create in one worktree today and a create
369
- in another worktree tomorrow collide just as surely, so long as neither
370
- worktree has seen the other's commit. Both creates succeed, both refuse
371
- nothing, and reconciliation reports nothing, because there is no recorded
372
- revision to compare. The collision becomes visible only when the branches are
373
- integrated: `validate` then fails globally with `duplicate-number` on every
374
- colliding item, and an invalid ledger blocks every mutation, so the whole
375
- ledger stops accepting work. `number` is immutable and `patch` correctly
376
- rejects it, so this protocol currently offers no operation that repairs the
377
- collision.
378
-
379
- Until both items ship, serialize `create` through a single worktree, and run
380
- `validate` immediately after you integrate branches so a collision surfaces at
381
- the merge rather than at the next mutation. Editing `number` in the item
382
- source by hand, committing it, and then running `claim-adopt` is **not a
383
- supported workaround**: one field deployment did exactly that as an emergency
426
+ `error.details.findings` names the item whose authorized create this checkout
427
+ cannot see, and each finding's own `remediation` string stays authoritative:
428
+ commit this worktree's bytes for `git-finalization-required`; synchronize the
429
+ named sibling revision when `owner_ref` names a live worktree; inspect the
430
+ reachable or dangling history and restore or explicitly adopt reviewed bytes
431
+ for a locally absent or reachable-unowned revision; and restore or adopt for
432
+ unauthorized bytes. The reproduced stale-sibling case is locally absent on both
433
+ surfaces, so it renders the cannot-be-established sentence, which is an
434
+ instruction to inspect and integrate, never an instruction to wait. Once the
435
+ ledger validates and `claim-verify` exits 0, retry the same request ID: the
436
+ refusal published no byte, so the retry is an ordinary no-clobber create and
437
+ takes the next number. The fence does not trap recovery — `claim-verify` stays
438
+ read-only, `claim-adopt` keeps its explicit override, claim release stays
439
+ available under barriers, and Git synchronization was never this tool's to
440
+ block.
441
+
442
+ **Recovery from an interrupted create.** An intent with no terminal resolves on
443
+ the next invocation from the item path alone: the candidate revision present
444
+ appends the committed terminal, the path absent appends the create abort, and
445
+ any third revision reports `legacy-mutation-outcome-unknown` as a global
446
+ barrier. Recovery never guesses a number and never publishes an item. Because
447
+ capacity for the intent, one clock entry, and one terminal is reserved before
448
+ the intent is appended, an exhausted journal refuses unchanged before
449
+ publication rather than stranding an unresolvable attempt.
450
+
451
+ **Create owns its item and its reconciliation log.** A successful create is
452
+ journal-visible from the item's birth, so its `changed_paths` and its
453
+ `--auto-commit` commit set are exactly the created item and
454
+ `<ledger>/.wowbagger/reconcile-<namespace>.md`. Owning the log it writes is not
455
+ permission to absorb residue: a reconciliation log already dirty before the
456
+ invocation is foreign, and create still refuses it.
457
+
458
+ **Commit every create before the next mutation.** A create has no authorized
459
+ predecessor — there is no earlier revision of an item that did not exist — so
460
+ Git `HEAD` is the only surface that can hold its authorized bytes, and an
461
+ uncommitted create reports the global `git-finalization-required` until it is
462
+ committed. A `patch` or `transition` may instead occupy the documented
463
+ authorized predecessor/successor window and run before its predecessor is
464
+ committed, but that window is an accepted overlap, not a licence: commit every
465
+ mutation before the next one. Alpha.14 ships no batch mutation. The supported
466
+ bulk pattern is the create-then-commit loop, one commit per created item, and
467
+ item #186 owns the design of a safe batch create.
468
+
469
+ **Cost.** A provisioned create already took the namespace lock, replayed the
470
+ journal, loaded the ledger, read Git `HEAD`, and reconciled. The fence changes
471
+ which findings refuse; it adds no new Git roster or history traversal to a
472
+ clean create, and the measured cost of the change is two extra fsync'd journal
473
+ appends. The durable cost is journal growth: each successful create adds one
474
+ intent and one committed terminal, so with no other activity the
475
+ 65,536-entry limit permits at most 21,845 three-entry create cycles and the
476
+ 8 MiB byte limit may bind first. Other claim and mutation activity lowers that
477
+ ceiling. Capacity is checked before publication and fails closed. Journal
478
+ compaction is not part of this change.
479
+
480
+ **A ledger that already carries duplicate numbers is item #182's recovery
481
+ work, not this fix's.** Such a ledger fails validation and refuses every
482
+ mutation before allocation, and item #182 owns the fenced repair. #181 prevents
483
+ new collisions in every valid ledger; it neither renumbers damaged items nor
484
+ makes an invalid ledger worse. Editing `number` in the item source by hand,
485
+ committing it, and then running `claim-adopt` is **not a supported
486
+ workaround**: one field deployment did exactly that as an emergency
384
487
  intervention during an outage, and it bypasses the number-collision and
385
488
  reference checks every mutation performs, so it can leave dangling
386
489
  `depends_on`, `related`, and parent references that nothing reports.
387
490
 
491
+ **What alpha.14 guarantees, and what it does not.** Alpha.14 closes the
492
+ reported PropertyCompass2 collision: cooperating alpha.14 worktrees of one
493
+ clone that share one Git common directory can no longer commit two items
494
+ carrying the same number, because every such create is visible through the
495
+ shared journal before publication even without branch integration. Separate
496
+ clones, separate machines, alpha.13 writers before the hard cutover, and
497
+ noncooperating writes stay outside that fence and still rely on branch
498
+ integration plus `validate`.
499
+
500
+ **The upgrade is a hard cutover.** Alpha.13 cannot read a `create-v1` legacy
501
+ intent. Executed against a ledger whose shared journal already carries one, its
502
+ create exits 6 with `error.code` `claim-store-unavailable`, message
503
+ `The durable claim store is unavailable.`, and `error.details.reason`
504
+ `claim-store-unreadable`, leaving the state unchanged and writing no item. That
505
+ fail-closed behavior is the compatibility guarantee: an old writer cannot
506
+ commit another duplicate. It is also the operational limit, because alpha.13 is
507
+ immutable and emits a generic unreadable-store message rather than upgrade
508
+ guidance. Read that exact refusal as **this repository was written by a newer
509
+ Wowbagger; upgrade this worktree to continue.** There is no automatic migration
510
+ and no mixed-version grace period: upgrade every writer in one Git coordination
511
+ domain before the first alpha.14 create. If a partial upgrade writes the new
512
+ grammar first, every remaining alpha.13 worktree stops making claim-protected
513
+ mutations until it is upgraded. Item #185 owns the general ability of a running
514
+ old binary to diagnose its own version drift; no change here can retrofit a
515
+ message into an already-published executable.
516
+
388
517
  **`unauthorized-revision` has two remedies, and only one of them is
389
518
  destructive.** Restoring the authorized revision discards the out-of-protocol
390
519
  edit. Adopting the committed revision keeps it and moves the coordinator's
@@ -401,6 +530,16 @@ Each refusal carries a `reason` that separates the cases:
401
530
  | `worktree-synchronization-required` | another worktree wrote the item | wait for the owner named by `owner_ref`/`owner_commit`, then synchronize this checkout and run `claim-verify`; if `owner_unavailable` is true, follow the finding's `remediation` (section 3.2 step 4) |
402
531
  | `unauthorized-revision` | the item was changed outside the protocol | restore the authorized revision and discard the edit, then `claim-verify`; or adopt the committed revision and keep the edit, then `claim-verify` (section 3.3) |
403
532
 
533
+ **Scope is the coordinator's judgement, not a published member.** What a
534
+ finding refuses — every write, only a write against the item it names, or
535
+ nothing — is decided from the topology and travels beside the finding, never
536
+ inside it. No response carries it. A consumer MUST NOT infer scope from the
537
+ `reason` string, from the `remediation` sentence, or from whether `owner_ref`
538
+ is present: those are diagnostics for a person, and reading them as a scope
539
+ signal is exactly how alpha.11 treated an out-of-protocol revision as advisory
540
+ synchronization. The only supported scope signal is the refusal itself: a
541
+ mutation that returns exit 6 was blocked, and one that returns exit 0 was not.
542
+
404
543
  Ownership on the current symbolic ref is never reported as a foreign worktree
405
544
  owner, and a detached `HEAD` applies the same guard through its reachable
406
545
  commit history. If the expected revision is already reachable from the current
@@ -409,16 +548,58 @@ worktree; it remains `unauthorized-revision` and blocks unrelated mutations.
409
548
  This distinguishes a real sibling's uncommitted successor from history the
410
549
  current checkout already owns.
411
550
 
551
+ **`owner_ref` names an active named worktree, and nothing else.** Owner
552
+ evidence is the worktree roster Git reports live, searched this checkout first,
553
+ then every live worktree that has a branch checked out, ordered by branch ref
554
+ and then by path so one revision carried by several live worktrees always names
555
+ the same owner. A bare record, a record Git marks prunable, and a worktree
556
+ whose `HEAD` names no commit are excluded. `owner_ref` is always the branch of
557
+ one of those live worktrees, and `owner_commit` is the commit in that
558
+ worktree's history whose item bytes are the expected revision.
559
+
560
+ Reachability alone is never ownership. A tag, a remote-tracking ref, a branch
561
+ no worktree has checked out, and a live worktree on a detached `HEAD` can each
562
+ reach the expected revision while naming nobody who could publish it. All of
563
+ them, and a revision no reachable commit carries at all, produce
564
+ `owner_unavailable: true` and no `owner_ref`. So `owner_unavailable` means one
565
+ of three things: the expected revision is not reachable at all, a live sibling
566
+ holds it on a detached `HEAD`, or it is reachable only from refs no active
567
+ worktree has checked out.
568
+
569
+ Three `owner_unavailable` remediation sentences separate those cases. The
570
+ sentence naming ownership that cannot be established from reachable refs is
571
+ emitted only when this checkout has no item path and `HEAD` has none either, so
572
+ the item has never existed here. A revision Git does reach — from a tag, a
573
+ remote-tracking ref, a branch no worktree has checked out, or a live worktree on
574
+ a detached `HEAD` — gets the sentence saying the revision is reachable in Git
575
+ while no active named worktree owner is established. Its remedy is to inspect
576
+ the reachable history and restore or explicitly adopt reviewed bytes, then run
577
+ `claim-verify`; there is no commit left to wait for. Only a revision no
578
+ reachable commit carries at all gets the not-yet-reachable sentence, whose
579
+ remedy is to wait for the owning worktree to commit, then synchronize this
580
+ checkout and run `claim-verify`.
581
+
582
+ Through alpha.13 the reachable cases were given the not-yet-reachable sentence
583
+ too, which told a reader to wait for a commit Git already had. The wait never
584
+ ended, because no named worktree was ever going to publish those bytes. The
585
+ `reason`, the codes, the scope, and the finding members are unchanged; only the
586
+ `remediation` text for the reachable-unowned cases is corrected.
587
+
412
588
  **Writer identity.** A legacy mutation and a claimed publication record the
413
589
  worktree that authorized them in their journal entries as
414
590
  `writer_worktree_id`: `legacy-mutation-intent`, `legacy-mutation`,
415
591
  `publish-intent`, and `publish-final`. The field is optional on all of them: an
416
- entry written before the field existed stays valid and attributes nothing. A
417
- publication resolves its identity once under the claim lock, so its intent and
418
- its terminal name one writer, and a terminal that recovery reconstructs after a
419
- lost response carries the identity its intent recorded. When the journal names
420
- the current worktree as the writer of the expected revision and no reachable
421
- ref carries that revision, no sibling can ever produce it, so the finding is
592
+ entry written before the field existed, which is every entry alpha.12 and
593
+ earlier wrote, stays valid, attributes nothing, and leaves the expected writer
594
+ `unknown`. Writer attribution alone therefore cannot turn such a finding into a
595
+ global barrier: it stays advisory `worktree-synchronization-required`. Local
596
+ state and current-checkout ownership are still judged first, and both remain
597
+ global barriers whatever the entry records. A publication resolves its identity
598
+ once under the claim lock, so its intent and its terminal name one writer, and
599
+ a terminal that recovery reconstructs after a lost response carries the
600
+ identity its intent recorded. When the journal names the current worktree as
601
+ the writer of the expected revision and no reachable ref carries that revision,
602
+ no sibling can ever produce it, so the finding is
422
603
  `unauthorized-revision` and blocks every mutation instead of
423
604
  `worktree-synchronization-required`, which blocks only its own item.
424
605
 
@@ -471,13 +652,15 @@ a new UUID, and the removed UUID stays in journal history attributing nothing.
471
652
  carries `owner_unavailable: true`.
472
653
  3. If an owner is named, WAIT for that owner to publish the commit, then
473
654
  synchronize this checkout to it. Do not merge unrelated live work.
474
- 4. If ownership is unavailable, the `remediation` string separates two cases.
655
+ 4. If ownership is unavailable, the `remediation` string separates three cases.
475
656
  When it says the expected revision is not yet reachable, the owning
476
657
  worktree has not committed it yet: wait as in step 3, then synchronize.
477
- When it says ownership cannot be established from reachable refs, inspect
478
- reachable or dangling commits. Restore the exact authorized bytes or use
479
- explicit `claim-adopt` authority after review. Do not copy peer
480
- working-tree bytes.
658
+ When it says the revision is reachable in Git while no active named worktree
659
+ owner is established, do not wait: inspect the reachable history that carries
660
+ it, then restore the exact authorized bytes or use explicit `claim-adopt`
661
+ authority after review. When it says ownership cannot be established from
662
+ reachable refs, inspect reachable or dangling commits and remedy it the same
663
+ way. In neither inspection case do you copy peer working-tree bytes.
481
664
  5. Run `claim-verify --ledger <dir> --json` in the blocked worktree and require
482
665
  exit 0.
483
666
  6. Resume the affected item.
@@ -487,6 +670,27 @@ successful mutation requires a valid ledger and no findings blocking the target
487
670
  item, not a globally empty findings list. Nonblocking findings remain available
488
671
  to `claim-verify`, where a caller that names no target still sees every finding.
489
672
 
673
+ **A target-scoped success is not a globally clean claim store.** The two scopes
674
+ answer different questions and a caller MUST NOT substitute one for the other.
675
+ A mutation, and auto-commit inside it, requires only that no finding blocks its
676
+ own target item. `claim-verify` names no target, so every unresolved finding
677
+ blocks it: exit 6 there is the repository-wide answer, and it stays exit 6
678
+ while any item in the repository carries a blocking finding, including one
679
+ belonging to work this caller has nothing to do with. A cooperating worker can
680
+ therefore commit every one of its own mutations while `claim-verify` never
681
+ reaches exit 0, because a sibling worktree's unpublished item is visible from
682
+ every checkout that shares the common directory.
683
+
684
+ That is current behavior, not a settled design. Item #184 is open in triage to
685
+ choose the supported surface — item-scoped verification, target-aware input, or
686
+ repository-wide success with structured foreign-work warnings, while keeping a
687
+ strict whole-repository mode. Until it ships, an automated gate that demands a
688
+ globally clean `claim-verify` is unsatisfiable in a repository with live
689
+ sibling work. Gate on the mutation's own refusal, and read a repository-wide
690
+ `claim-verify` as the diagnostic it is. Do not hand-edit an item or run
691
+ `claim-adopt` to reach exit 0: adoption moves the coordinator's authorized
692
+ revision and is not a way to silence a sibling's finding.
693
+
490
694
  `claim-verify` in the writing worktree finalizes that worktree's own state; it
491
695
  does not make a sibling checkout able to see the item. Synchronization or
492
696
  explicit recovery is still required for the affected item.
@@ -666,6 +870,23 @@ fails or its durability is uncertain, the backend returns exit 6 with
666
870
  to guess. It cannot report a lease success or semantic rejection whose time
667
871
  was not persisted.
668
872
 
873
+ **A refused reconciliation has already advanced the floor.** Reconciliation
874
+ persists its clock entry before it classifies anything, so a command that then
875
+ refuses with exit 6 `claim-store-unavailable`, reason
876
+ `publication-reconciliation-required`, has still moved the durable floor to
877
+ `max(physical_utc, previous_floor)`. No claim, epoch, or ledger byte changes,
878
+ and the refusal reports none. The floor is monotonic, so the effect is on time
879
+ alone and it is permanent: once a refused reconciliation has published a floor
880
+ at or past a lease's `expires_at`, that lease can never read live again, and a
881
+ later acquire, renew, or fence check on the same tuple observes it expired.
882
+ On a ledger whose floor already runs ahead of this caller's wall clock, every
883
+ refused reconciliation republishes the higher floor, so a holder whose lease
884
+ looks live by its own clock may find it expired the moment it asks. A caller
885
+ that meets a barrier during a long recovery SHOULD expect to reacquire rather
886
+ than assume its lease survived. This is section 5's rule for a rejected
887
+ decision applied one step earlier: an advanced floor is never evidence that
888
+ anything succeeded.
889
+
669
890
  When `last_epoch` is `18446744073709551615` (the unsigned 64-bit maximum), a
670
891
  new acquire or takeover is impossible. After the authoritative decision time
671
892
  has been persisted, the backend returns exit 6 `epoch-exhausted` with message
@@ -1095,18 +1316,24 @@ create MUST reject any item identity whose tuple has claim history with exit 4
1095
1316
  high-water mark. Both checks persist authoritative decision time before their
1096
1317
  response.
1097
1318
 
1098
- Before a merge-coordinated backend permits a legacy transition or patch to
1099
- write bytes, it MUST fsync a `legacy-mutation-intent` with the expected and
1100
- candidate revisions. Under the same namespace lock, it MUST reserve journal
1101
- capacity for that intent, one recovery clock entry, and one terminal entry. A
1102
- committed write appends `legacy-mutation`; an unchanged write appends
1103
- `legacy-mutation-abort`. Reconciliation resolves a pending intent to the
1104
- committed terminal when the candidate revision is present, or to the abort
1319
+ Before a merge-coordinated backend permits a legacy create, transition, or
1320
+ patch to write bytes, it MUST fsync a `legacy-mutation-intent` carrying the
1321
+ expected and candidate revisions. Under the same namespace lock, it MUST
1322
+ reserve journal capacity for that intent, one recovery clock entry, and one
1323
+ terminal entry. A committed write appends `legacy-mutation`; an unchanged write
1324
+ appends `legacy-mutation-abort`. Reconciliation resolves a pending intent to
1325
+ the committed terminal when the candidate revision is present, or to the abort
1105
1326
  terminal when the expected revision remains. Any third revision produces
1106
1327
  `legacy-mutation-outcome-unknown`. The latest committed terminal is the
1107
1328
  authorized expected revision. A later unrecorded revision remains a stale
1108
1329
  write.
1109
1330
 
1331
+ A create has no predecessor, so its intent carries `expected_revision: null`,
1332
+ which is valid only for `create-v1`, and the item path's absence is what
1333
+ resolves it to the abort terminal. Section 3.1 states the complete create
1334
+ ordering, its allocation fence, and the alpha.13 cutover the widened grammar
1335
+ forces.
1336
+
1110
1337
  A merge-coordinated backend MUST reconcile before it authorizes a legacy write,
1111
1338
  and section 6 states the same requirement for `publish-claimed`.
1112
1339
  When reconciliation produces any blocking finding, the legacy command MUST
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wowbagger",
3
- "version": "0.1.0-alpha.13",
3
+ "version": "0.1.0-alpha.14",
4
4
  "description": "Git-native work ledger for coding agents: deterministic ready queues, guarded CAS mutations, claims and fencing, and self-contained HTML reports.",
5
5
  "type": "module",
6
6
  "license": "MIT",