wowbagger 0.1.0-alpha.13 → 0.1.0-alpha.17

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,56 +334,183 @@ 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
384
- intervention during an outage, and it bypasses the number-collision and
385
- reference checks every mutation performs, so it can leave dangling
386
- `depends_on`, `related`, and parent references that nothing reports.
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 recovery work and
481
+ is repaired through the separate `ledger-repair` contract, not through claim
482
+ operations. Run `number-repair-proposal --ledger <dir> --json`, review the
483
+ complete mapping, then run `number-repair --ledger <dir> --input <repair.json>
484
+ --json`. The repair command runs only when duplicate-number errors are the
485
+ complete validation failure, preserves ULID identities and relation values, and
486
+ publishes all affected items under the shared namespace fence. Arbitrary hand
487
+ edits remain unsupported because they can damage IDs, paths, or references.
488
+
489
+ **What alpha.14 guarantees, and what it does not.** Alpha.14 closes the
490
+ reported PropertyCompass2 collision: cooperating alpha.14 worktrees of one
491
+ clone that share one Git common directory can no longer commit two items
492
+ carrying the same number, because every such create is visible through the
493
+ shared journal before publication even without branch integration. Separate
494
+ clones, separate machines, alpha.13 writers before the hard cutover, and
495
+ noncooperating writes stay outside that fence and still rely on branch
496
+ integration plus `validate`.
497
+
498
+ **The upgrade is a hard cutover.** Alpha.13 cannot read a `create-v1` legacy
499
+ intent. Executed against a ledger whose shared journal already carries one, its
500
+ create exits 6 with `error.code` `claim-store-unavailable`, message
501
+ `The durable claim store is unavailable.`, and `error.details.reason`
502
+ `claim-store-unreadable`, leaving the state unchanged and writing no item. That
503
+ fail-closed behavior is the compatibility guarantee: an old writer cannot
504
+ commit another duplicate. It is also the operational limit, because alpha.13 is
505
+ immutable and emits a generic unreadable-store message rather than upgrade
506
+ guidance. Read that exact refusal as **this repository was written by a newer
507
+ Wowbagger; upgrade this worktree to continue.** There is no automatic migration
508
+ and no mixed-version grace period: upgrade every writer in one Git coordination
509
+ domain before the first alpha.14 create. If a partial upgrade writes the new
510
+ grammar first, every remaining alpha.13 worktree stops making claim-protected
511
+ mutations until it is upgraded. Item #185 owns the general ability of a running
512
+ old binary to diagnose its own version drift; no change here can retrofit a
513
+ message into an already-published executable.
387
514
 
388
515
  **`unauthorized-revision` has two remedies, and only one of them is
389
516
  destructive.** Restoring the authorized revision discards the out-of-protocol
@@ -401,6 +528,16 @@ Each refusal carries a `reason` that separates the cases:
401
528
  | `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
529
  | `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
530
 
531
+ **Scope is the coordinator's judgement, not a published member.** What a
532
+ finding refuses — every write, only a write against the item it names, or
533
+ nothing — is decided from the topology and travels beside the finding, never
534
+ inside it. No response carries it. A consumer MUST NOT infer scope from the
535
+ `reason` string, from the `remediation` sentence, or from whether `owner_ref`
536
+ is present: those are diagnostics for a person, and reading them as a scope
537
+ signal is exactly how alpha.11 treated an out-of-protocol revision as advisory
538
+ synchronization. The only supported scope signal is the refusal itself: a
539
+ mutation that returns exit 6 was blocked, and one that returns exit 0 was not.
540
+
404
541
  Ownership on the current symbolic ref is never reported as a foreign worktree
405
542
  owner, and a detached `HEAD` applies the same guard through its reachable
406
543
  commit history. If the expected revision is already reachable from the current
@@ -409,16 +546,58 @@ worktree; it remains `unauthorized-revision` and blocks unrelated mutations.
409
546
  This distinguishes a real sibling's uncommitted successor from history the
410
547
  current checkout already owns.
411
548
 
549
+ **`owner_ref` names an active named worktree, and nothing else.** Owner
550
+ evidence is the worktree roster Git reports live, searched this checkout first,
551
+ then every live worktree that has a branch checked out, ordered by branch ref
552
+ and then by path so one revision carried by several live worktrees always names
553
+ the same owner. A bare record, a record Git marks prunable, and a worktree
554
+ whose `HEAD` names no commit are excluded. `owner_ref` is always the branch of
555
+ one of those live worktrees, and `owner_commit` is the commit in that
556
+ worktree's history whose item bytes are the expected revision.
557
+
558
+ Reachability alone is never ownership. A tag, a remote-tracking ref, a branch
559
+ no worktree has checked out, and a live worktree on a detached `HEAD` can each
560
+ reach the expected revision while naming nobody who could publish it. All of
561
+ them, and a revision no reachable commit carries at all, produce
562
+ `owner_unavailable: true` and no `owner_ref`. So `owner_unavailable` means one
563
+ of three things: the expected revision is not reachable at all, a live sibling
564
+ holds it on a detached `HEAD`, or it is reachable only from refs no active
565
+ worktree has checked out.
566
+
567
+ Three `owner_unavailable` remediation sentences separate those cases. The
568
+ sentence naming ownership that cannot be established from reachable refs is
569
+ emitted only when this checkout has no item path and `HEAD` has none either, so
570
+ the item has never existed here. A revision Git does reach — from a tag, a
571
+ remote-tracking ref, a branch no worktree has checked out, or a live worktree on
572
+ a detached `HEAD` — gets the sentence saying the revision is reachable in Git
573
+ while no active named worktree owner is established. Its remedy is to inspect
574
+ the reachable history and restore or explicitly adopt reviewed bytes, then run
575
+ `claim-verify`; there is no commit left to wait for. Only a revision no
576
+ reachable commit carries at all gets the not-yet-reachable sentence, whose
577
+ remedy is to wait for the owning worktree to commit, then synchronize this
578
+ checkout and run `claim-verify`.
579
+
580
+ Through alpha.13 the reachable cases were given the not-yet-reachable sentence
581
+ too, which told a reader to wait for a commit Git already had. The wait never
582
+ ended, because no named worktree was ever going to publish those bytes. The
583
+ `reason`, the codes, the scope, and the finding members are unchanged; only the
584
+ `remediation` text for the reachable-unowned cases is corrected.
585
+
412
586
  **Writer identity.** A legacy mutation and a claimed publication record the
413
587
  worktree that authorized them in their journal entries as
414
588
  `writer_worktree_id`: `legacy-mutation-intent`, `legacy-mutation`,
415
589
  `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
590
+ entry written before the field existed, which is every entry alpha.12 and
591
+ earlier wrote, stays valid, attributes nothing, and leaves the expected writer
592
+ `unknown`. Writer attribution alone therefore cannot turn such a finding into a
593
+ global barrier: it stays advisory `worktree-synchronization-required`. Local
594
+ state and current-checkout ownership are still judged first, and both remain
595
+ global barriers whatever the entry records. A publication resolves its identity
596
+ once under the claim lock, so its intent and its terminal name one writer, and
597
+ a terminal that recovery reconstructs after a lost response carries the
598
+ identity its intent recorded. When the journal names the current worktree as
599
+ the writer of the expected revision and no reachable ref carries that revision,
600
+ no sibling can ever produce it, so the finding is
422
601
  `unauthorized-revision` and blocks every mutation instead of
423
602
  `worktree-synchronization-required`, which blocks only its own item.
424
603
 
@@ -471,13 +650,15 @@ a new UUID, and the removed UUID stays in journal history attributing nothing.
471
650
  carries `owner_unavailable: true`.
472
651
  3. If an owner is named, WAIT for that owner to publish the commit, then
473
652
  synchronize this checkout to it. Do not merge unrelated live work.
474
- 4. If ownership is unavailable, the `remediation` string separates two cases.
653
+ 4. If ownership is unavailable, the `remediation` string separates three cases.
475
654
  When it says the expected revision is not yet reachable, the owning
476
655
  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.
656
+ When it says the revision is reachable in Git while no active named worktree
657
+ owner is established, do not wait: inspect the reachable history that carries
658
+ it, then restore the exact authorized bytes or use explicit `claim-adopt`
659
+ authority after review. When it says ownership cannot be established from
660
+ reachable refs, inspect reachable or dangling commits and remedy it the same
661
+ way. In neither inspection case do you copy peer working-tree bytes.
481
662
  5. Run `claim-verify --ledger <dir> --json` in the blocked worktree and require
482
663
  exit 0.
483
664
  6. Resume the affected item.
@@ -487,6 +668,27 @@ successful mutation requires a valid ledger and no findings blocking the target
487
668
  item, not a globally empty findings list. Nonblocking findings remain available
488
669
  to `claim-verify`, where a caller that names no target still sees every finding.
489
670
 
671
+ **A target-scoped success is not a globally clean claim store.** The two scopes
672
+ answer different questions and a caller MUST NOT substitute one for the other.
673
+ A mutation, and auto-commit inside it, requires only that no finding blocks its
674
+ own target item. `claim-verify` names no target, so every unresolved finding
675
+ blocks it: exit 6 there is the repository-wide answer, and it stays exit 6
676
+ while any item in the repository carries a blocking finding, including one
677
+ belonging to work this caller has nothing to do with. A cooperating worker can
678
+ therefore commit every one of its own mutations while `claim-verify` never
679
+ reaches exit 0, because a sibling worktree's unpublished item is visible from
680
+ every checkout that shares the common directory.
681
+
682
+ That is current behavior, not a settled design. Item #184 is open in triage to
683
+ choose the supported surface — item-scoped verification, target-aware input, or
684
+ repository-wide success with structured foreign-work warnings, while keeping a
685
+ strict whole-repository mode. Until it ships, an automated gate that demands a
686
+ globally clean `claim-verify` is unsatisfiable in a repository with live
687
+ sibling work. Gate on the mutation's own refusal, and read a repository-wide
688
+ `claim-verify` as the diagnostic it is. Do not hand-edit an item or run
689
+ `claim-adopt` to reach exit 0: adoption moves the coordinator's authorized
690
+ revision and is not a way to silence a sibling's finding.
691
+
490
692
  `claim-verify` in the writing worktree finalizes that worktree's own state; it
491
693
  does not make a sibling checkout able to see the item. Synchronization or
492
694
  explicit recovery is still required for the affected item.
@@ -666,6 +868,23 @@ fails or its durability is uncertain, the backend returns exit 6 with
666
868
  to guess. It cannot report a lease success or semantic rejection whose time
667
869
  was not persisted.
668
870
 
871
+ **A refused reconciliation has already advanced the floor.** Reconciliation
872
+ persists its clock entry before it classifies anything, so a command that then
873
+ refuses with exit 6 `claim-store-unavailable`, reason
874
+ `publication-reconciliation-required`, has still moved the durable floor to
875
+ `max(physical_utc, previous_floor)`. No claim, epoch, or ledger byte changes,
876
+ and the refusal reports none. The floor is monotonic, so the effect is on time
877
+ alone and it is permanent: once a refused reconciliation has published a floor
878
+ at or past a lease's `expires_at`, that lease can never read live again, and a
879
+ later acquire, renew, or fence check on the same tuple observes it expired.
880
+ On a ledger whose floor already runs ahead of this caller's wall clock, every
881
+ refused reconciliation republishes the higher floor, so a holder whose lease
882
+ looks live by its own clock may find it expired the moment it asks. A caller
883
+ that meets a barrier during a long recovery SHOULD expect to reacquire rather
884
+ than assume its lease survived. This is section 5's rule for a rejected
885
+ decision applied one step earlier: an advanced floor is never evidence that
886
+ anything succeeded.
887
+
669
888
  When `last_epoch` is `18446744073709551615` (the unsigned 64-bit maximum), a
670
889
  new acquire or takeover is impossible. After the authoritative decision time
671
890
  has been persisted, the backend returns exit 6 `epoch-exhausted` with message
@@ -1095,18 +1314,24 @@ create MUST reject any item identity whose tuple has claim history with exit 4
1095
1314
  high-water mark. Both checks persist authoritative decision time before their
1096
1315
  response.
1097
1316
 
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
1317
+ Before a merge-coordinated backend permits a legacy create, transition, or
1318
+ patch to write bytes, it MUST fsync a `legacy-mutation-intent` carrying the
1319
+ expected and candidate revisions. Under the same namespace lock, it MUST
1320
+ reserve journal capacity for that intent, one recovery clock entry, and one
1321
+ terminal entry. A committed write appends `legacy-mutation`; an unchanged write
1322
+ appends `legacy-mutation-abort`. Reconciliation resolves a pending intent to
1323
+ the committed terminal when the candidate revision is present, or to the abort
1105
1324
  terminal when the expected revision remains. Any third revision produces
1106
1325
  `legacy-mutation-outcome-unknown`. The latest committed terminal is the
1107
1326
  authorized expected revision. A later unrecorded revision remains a stale
1108
1327
  write.
1109
1328
 
1329
+ A create has no predecessor, so its intent carries `expected_revision: null`,
1330
+ which is valid only for `create-v1`, and the item path's absence is what
1331
+ resolves it to the abort terminal. Section 3.1 states the complete create
1332
+ ordering, its allocation fence, and the alpha.13 cutover the widened grammar
1333
+ forces.
1334
+
1110
1335
  A merge-coordinated backend MUST reconcile before it authorizes a legacy write,
1111
1336
  and section 6 states the same requirement for `publish-claimed`.
1112
1337
  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.17",
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",
@@ -30,7 +30,7 @@
30
30
  "cli"
31
31
  ],
32
32
  "engines": {
33
- "node": ">=20"
33
+ "node": ">=24"
34
34
  },
35
35
  "bin": {
36
36
  "wowbagger": "bin/wowbagger.js"
@@ -25,7 +25,8 @@
25
25
  "inspect",
26
26
  "list",
27
27
  "mint-id",
28
- "report"
28
+ "report",
29
+ "version-drift"
29
30
  ]
30
31
  },
31
32
  "contract_version": {
@@ -55,8 +56,8 @@
55
56
  "capabilities",
56
57
  "inspect",
57
58
  "list",
58
- "mint-id",
59
- "report"
59
+ "report",
60
+ "version-drift"
60
61
  ]
61
62
  },
62
63
  "contract_version": {
@@ -92,6 +92,24 @@
92
92
  "version": 1,
93
93
  "summary": "Legacy-write fence refusals in the ledger-mutation namespace."
94
94
  },
95
+ {
96
+ "file": "ledger-repair-proposal.json",
97
+ "domain": "ledger-repair",
98
+ "version": 1,
99
+ "summary": "The read-only duplicate-number repair proposal result."
100
+ },
101
+ {
102
+ "file": "ledger-repair-request.json",
103
+ "domain": "ledger-repair",
104
+ "version": 1,
105
+ "summary": "The strict number-repair request, covering the complete duplicate set."
106
+ },
107
+ {
108
+ "file": "ledger-repair-response.json",
109
+ "domain": "ledger-repair",
110
+ "version": 1,
111
+ "summary": "Every ledger-repair response: the proposal and apply envelopes at repair version 1."
112
+ },
95
113
  {
96
114
  "file": "report-config-v1.json",
97
115
  "domain": "report-config",