wowbagger 0.1.0-alpha.9 → 0.5.0
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/CHANGELOG.md +509 -0
- package/README.md +272 -136
- package/docs/adapter-contract.md +1 -1
- package/docs/host-contract.md +7 -1
- package/docs/mutation-contract.md +354 -82
- package/docs/work-claim-contract.md +466 -88
- package/package.json +2 -2
- package/schemas/core-capabilities-response.json +1 -1
- package/schemas/core-envelope.json +4 -3
- package/schemas/index.json +18 -0
- package/schemas/ledger-repair-proposal.json +170 -0
- package/schemas/ledger-repair-request.json +61 -0
- package/schemas/ledger-repair-response.json +90 -0
- package/schemas/report-config-v1.json +4 -0
- package/schemas/report-config-v2.json +5 -0
- package/skills/wowbagger/SKILL.md +242 -59
- package/src/adapter/core-probe.js +3 -4
- package/src/adapter/process-outcome.js +8 -1
- package/src/claim-capabilities.js +3 -3
- package/src/claim-coordinator.js +61 -12
- package/src/claim-journal.js +212 -7
- package/src/claim-prospective.js +1 -28
- package/src/claim-publication.js +412 -78
- package/src/claim-request.js +9 -0
- package/src/claim-store.js +9 -4
- package/src/cli.js +302 -56
- package/src/extensions.js +1 -0
- package/src/git-autocommit.js +106 -43
- package/src/git-reconciliation.js +74 -19
- package/src/git-worktrees.js +73 -0
- package/src/instrumentation.js +1 -0
- package/src/launch.js +2 -2
- package/src/ledger-repair.js +1170 -0
- package/src/mutation.js +73 -15
- package/src/reconciliation-classifier.js +117 -0
- package/src/report-evidence.js +158 -41
- package/src/report-graph.js +201 -73
- package/src/report-html.js +358 -155
- package/src/report-impact.js +106 -0
- package/src/report-selection.js +97 -0
- package/src/report-sequencing.js +4 -4
- package/src/report-svg.js +74 -20
- package/src/report-view.js +14 -1
- package/src/report.js +109 -23
- package/src/version-drift.js +98 -0
- package/src/worktree-identity.js +165 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# Work-claim contract
|
|
2
2
|
|
|
3
3
|
Status: accepted protocol design. The standalone Wowbagger CLI implements the
|
|
4
|
-
version
|
|
4
|
+
version 3 claim operations and the merge-coordinated Git-journal profile for
|
|
5
5
|
provisioned Git-backed ledgers. The no-I/O reference model and conformance
|
|
6
6
|
fixtures remain the oracle for the strict fenced protocol.
|
|
7
7
|
|
|
8
|
-
This document defines version
|
|
8
|
+
This document defines version 3 of the transport-neutral work-claim and
|
|
9
9
|
claimed-publication API, plus the merge-coordinated capability profile. The
|
|
10
10
|
words MUST, MUST NOT, SHOULD, and MAY are normative. JSON examples show objects
|
|
11
11
|
before compact serialization; a CLI prints exactly one compact JSON object
|
|
@@ -23,6 +23,21 @@ Generic consumers migrate without a wire change: they first identify the
|
|
|
23
23
|
work-claim envelope by `namespace: "work-claim"`, then require the advertised
|
|
24
24
|
`api_version`.
|
|
25
25
|
|
|
26
|
+
### Version 3
|
|
27
|
+
|
|
28
|
+
Version 3 retains every version 2 request, response, state, exit, fencing, and
|
|
29
|
+
recovery rule except for targeted verification. `claim-verify` accepts optional
|
|
30
|
+
`--id <wb_...>` without requiring that item to exist in the local checkout.
|
|
31
|
+
Bare verification remains strict repository-wide mode.
|
|
32
|
+
|
|
33
|
+
Every success result adds `verification_scope`: `{"mode":"repository"}` for
|
|
34
|
+
the bare command, or `{"mode":"target-item","item_id":"wb_..."}` for a target.
|
|
35
|
+
Every returned finding adds `blocks_verification_scope`. Target mode keeps all
|
|
36
|
+
repository findings visible but derives `ok`, exit status, and `state` only
|
|
37
|
+
from findings that block that target. Global safety findings still block every
|
|
38
|
+
target. This additive response and new CLI input move the negotiated API while
|
|
39
|
+
the top-level claim-envelope `contract_version` remains `1`.
|
|
40
|
+
|
|
26
41
|
### Version 2
|
|
27
42
|
|
|
28
43
|
Version 2 retains every version 1 request, response, state, exit, fencing, and
|
|
@@ -86,20 +101,25 @@ carries one operating rule that binds every caller:
|
|
|
86
101
|
|
|
87
102
|
**Commit each mutation to Git before running the next mutating command.**
|
|
88
103
|
|
|
89
|
-
A merge-coordinated backend
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
`
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
104
|
+
A merge-coordinated backend reconciles recorded revisions with Git `HEAD` and
|
|
105
|
+
the working tree. It refuses the next mutation when it finds an
|
|
106
|
+
`unauthorized-revision`, requires Git finalization, or requires synchronization
|
|
107
|
+
for the target item. A `worktree-synchronization-required` finding on an
|
|
108
|
+
unrelated item remains visible without blocking the command.
|
|
109
|
+
|
|
110
|
+
An existing item's latest authorized working-tree bytes and an earlier
|
|
111
|
+
authorized revision at `HEAD` form an authorized predecessor/successor window.
|
|
112
|
+
That window produces no finding, so another mutation can run before the first
|
|
113
|
+
one is committed. Acceptance is not finalization: callers still follow write,
|
|
114
|
+
commit, `claim-verify`, next write.
|
|
115
|
+
|
|
116
|
+
`claim-verify` is the reconciliation procedure for every refusal. Section 6
|
|
117
|
+
defines `claim-verify`, section 7 defines the refusal the legacy write paths
|
|
118
|
+
emit, and section 8 defines the error envelope. The [mutation
|
|
100
119
|
contract](mutation-contract.md) section 12 states the same rule for
|
|
101
|
-
`create`, `transition`, and `patch` callers,
|
|
102
|
-
considered-and-rejected alternative of validating
|
|
120
|
+
`create`, `transition`, `parent-migrate`, `snooze`, and `patch` callers,
|
|
121
|
+
together with the considered-and-rejected alternative of validating only
|
|
122
|
+
against working-tree bytes.
|
|
103
123
|
|
|
104
124
|
The optional `--auto-commit` flag performs that whole loop inside one
|
|
105
125
|
invocation on a provisioned ledger: it runs the pre-mutation `claim-verify`,
|
|
@@ -123,6 +143,15 @@ runs `claim-verify`. Repeating it creates no second commit. [Mutation
|
|
|
123
143
|
contract](mutation-contract.md) section 13 defines the flag, its strict
|
|
124
144
|
preflight, its commit sets, its failure envelopes, and this command.
|
|
125
145
|
|
|
146
|
+
When a provisioned ledger is opened in a fresh Git clone, the local claim
|
|
147
|
+
journal may not exist even though `HEAD` carries a committed reconciliation
|
|
148
|
+
log. The first reconciliation hydrates the local journal from that committed
|
|
149
|
+
projection, preserving its sequence gaps with non-projected clock entries. A
|
|
150
|
+
lock-free claim read projects the same committed evidence in memory without
|
|
151
|
+
writing the journal. This makes committed claim and publication history visible
|
|
152
|
+
without inventing new authority; later writes still require the clone's own
|
|
153
|
+
Git history to finalize them.
|
|
154
|
+
|
|
126
155
|
## 2. Ledger namespace and identity
|
|
127
156
|
|
|
128
157
|
Every claim key is the immutable tuple `(ledger_namespace, item_id)`. No state,
|
|
@@ -178,7 +207,7 @@ most `18446744073709551615`. Epochs never wrap, decrement, or get reused.
|
|
|
178
207
|
"operations": {
|
|
179
208
|
"work_claim": {
|
|
180
209
|
"supported": true,
|
|
181
|
-
"api_version":
|
|
210
|
+
"api_version": 3,
|
|
182
211
|
"mode": "fenced",
|
|
183
212
|
"claim_protected_publication": true,
|
|
184
213
|
"fencing_enforced_at": "ledger-publication-commit-boundary",
|
|
@@ -268,7 +297,7 @@ A provisioned Git-journal backend MAY instead report:
|
|
|
268
297
|
"operations": {
|
|
269
298
|
"work_claim": {
|
|
270
299
|
"supported": true,
|
|
271
|
-
"api_version":
|
|
300
|
+
"api_version": 3,
|
|
272
301
|
"mode": "merge-coordinated",
|
|
273
302
|
"claim_protected_publication": true,
|
|
274
303
|
"fencing_enforced_at": "git-history-reconciliation",
|
|
@@ -313,38 +342,199 @@ the local working tree and the local Git HEAD. A revision written in another
|
|
|
313
342
|
worktree is absent in both, so reconciliation reports
|
|
314
343
|
`stale-write-detected` and the mutation refuses with exit 6
|
|
315
344
|
`claim-store-unavailable`, reason `publication-reconciliation-required` only
|
|
316
|
-
when the mutation targets that item. `claim-verify` remains
|
|
317
|
-
reports every unresolved finding
|
|
345
|
+
when the mutation targets that item. Bare `claim-verify` remains
|
|
346
|
+
repository-wide. `claim-verify --id <item>` reports every unresolved finding
|
|
347
|
+
but blocks only on that target and global safety barriers.
|
|
318
348
|
|
|
319
349
|
The plain statement: **a recorded private write in one worktree blocks
|
|
320
350
|
mutations targeting that item, not unrelated item mutations in sibling
|
|
321
351
|
worktrees.** Own uncommitted work and out-of-protocol revisions remain global
|
|
322
352
|
safety barriers. This is item-scoped availability, not exclusive dispatch.
|
|
353
|
+
`create` reads that same reconciliation with one extra barrier, because it is
|
|
354
|
+
the one mutation whose output depends on an identity no caller supplied.
|
|
355
|
+
|
|
356
|
+
**Superseded: create no longer stays journal-silent.** Through alpha.13 this
|
|
357
|
+
contract decided that create records nothing, and three reasons held that
|
|
358
|
+
decision: create already protects its own instant, because publication is
|
|
359
|
+
atomic, refuses to clobber an existing path, and verifies the published bytes
|
|
360
|
+
exactly; a journaled create would serialize every worktree on the
|
|
361
|
+
highest-volume mutation; and the remaining exposure window was judged narrow
|
|
362
|
+
because it closed at the item's first journal-visible mutation.
|
|
363
|
+
|
|
364
|
+
Item #181 overturns that decision. The first reason answers the wrong
|
|
365
|
+
question. Create's atomic instant protects the path it publishes to, and
|
|
366
|
+
nothing protected the `number` it derives. A schema-version-2 create takes
|
|
367
|
+
`1 + max(existing numbers)` from the items its own checkout holds, so two
|
|
368
|
+
worktrees that have not integrated each other's commits derive the same number
|
|
369
|
+
and both publish. They need not overlap in time: a create today and a create
|
|
370
|
+
tomorrow collide as reliably as two simultaneous ones, because the loser is a
|
|
371
|
+
stale checkout rather than a lost race, and a wider mutex cannot fix a
|
|
372
|
+
sequential failure. Only durable evidence can. `number` is immutable and
|
|
373
|
+
`patch` correctly rejects it, so an undetected collision surfaces at
|
|
374
|
+
integration as a global `duplicate-number` validation failure that stops the
|
|
375
|
+
whole ledger.
|
|
376
|
+
|
|
377
|
+
**The fixed ordering.** On a provisioned ledger a create is a legacy mutation
|
|
378
|
+
like any other, and the shared namespace lock is held across every step:
|
|
379
|
+
|
|
380
|
+
1. Acquire the shared namespace lock.
|
|
381
|
+
2. Reconcile the repository state and apply the allocation fence below.
|
|
382
|
+
3. Load this worktree's ledger.
|
|
383
|
+
4. Acquire the item, relation, and number-index lock closure.
|
|
384
|
+
5. Reload and validate the ledger under those locks.
|
|
385
|
+
6. Derive `1 + max(existing numbers)`.
|
|
386
|
+
7. Serialize the candidate item.
|
|
387
|
+
8. Validate the complete candidate ledger.
|
|
388
|
+
9. Reserve journal capacity, then append the create intent.
|
|
389
|
+
10. Publish with atomic no-clobber semantics.
|
|
390
|
+
11. Verify the final path holds the exact candidate bytes.
|
|
391
|
+
12. Append the committed or aborted terminal.
|
|
392
|
+
|
|
393
|
+
A create appends its `legacy-mutation-intent` with `command: "create-v1"`
|
|
394
|
+
before any byte reaches the item path, and it appends that intent only after
|
|
395
|
+
step 8, so a refusal the command would have returned anyway records no
|
|
396
|
+
attempt. The intent carries `expected_revision: null`, which is valid only for
|
|
397
|
+
`create-v1`; every other command still requires a string revision. The
|
|
398
|
+
committed terminal is `legacy-mutation` with `command: "create-v1"`. The
|
|
399
|
+
create abort carries `command: "create-v1"` and `observed_revision: null`,
|
|
400
|
+
while an abort for any other command remains valid without `command` and still
|
|
401
|
+
requires a string revision. No entry records the assigned number: the
|
|
402
|
+
candidate revision binds the complete item bytes, and the ledger stays the one
|
|
403
|
+
number authority.
|
|
404
|
+
|
|
405
|
+
**The allocation fence.** Create reconciles with its own item as the target,
|
|
406
|
+
exactly like every other mutation, and one extra barrier reads the result.
|
|
407
|
+
Every global finding blocks create because it blocks every write:
|
|
408
|
+
`git-finalization-required` for this worktree's own uncommitted authorized
|
|
409
|
+
bytes, `unauthorized-revision` for out-of-protocol bytes, and an unresolved
|
|
410
|
+
`legacy-mutation-outcome-unknown`. On top of those, any coordinated item this
|
|
411
|
+
checkout does not hold blocks `create`, because the journal records a
|
|
412
|
+
committed terminal for an item whose number this working ledger cannot read,
|
|
413
|
+
so the next number derived here may be one a sibling already published. A
|
|
414
|
+
stale revision of an item this checkout holds does not block `create`: a
|
|
415
|
+
number is immutable, so the local maximum is already correct, and that finding
|
|
416
|
+
keeps its ordinary target scope.
|
|
417
|
+
|
|
418
|
+
**The old exposure window is closed on a provisioned ledger.** The journal used
|
|
419
|
+
to learn of an item only at its first `transition` or `patch`, so an
|
|
420
|
+
out-of-protocol overwrite before that mutation went unreported. A journaled
|
|
421
|
+
create records an authorized revision at the item's birth, so the ordinary
|
|
422
|
+
surfaces cover the item from then on: an out-of-protocol overwrite reports
|
|
423
|
+
`unauthorized-revision` and the next mutation refuses. An unprovisioned or
|
|
424
|
+
non-Git ledger keeps only its local number-index lock and its atomic
|
|
425
|
+
no-clobber publication; nothing there coordinates across checkouts, and nothing
|
|
426
|
+
here defends against an actor that bypasses this tool.
|
|
427
|
+
|
|
428
|
+
**The refusal.** A fenced create refuses before it allocates a number and
|
|
429
|
+
before it writes a byte:
|
|
430
|
+
|
|
431
|
+
```text
|
|
432
|
+
exit: 6
|
|
433
|
+
ok: false
|
|
434
|
+
namespace: "ledger-mutation"
|
|
435
|
+
command: "create-v1"
|
|
436
|
+
contract_version: 1
|
|
437
|
+
state: "unchanged"
|
|
438
|
+
error.code: "claim-store-unavailable"
|
|
439
|
+
error.details.reason: "publication-reconciliation-required"
|
|
440
|
+
```
|
|
323
441
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
442
|
+
`error.details.findings` names the item whose authorized create this checkout
|
|
443
|
+
cannot see, and each finding's own `remediation` string stays authoritative:
|
|
444
|
+
commit this worktree's bytes for `git-finalization-required`; synchronize the
|
|
445
|
+
named sibling revision when `owner_ref` names a live worktree; inspect the
|
|
446
|
+
reachable or dangling history and restore or explicitly adopt reviewed bytes
|
|
447
|
+
for a locally absent or reachable-unowned revision; and restore or adopt for
|
|
448
|
+
unauthorized bytes. The reproduced stale-sibling case is locally absent on both
|
|
449
|
+
surfaces, so it renders the cannot-be-established sentence, which is an
|
|
450
|
+
instruction to inspect and integrate, never an instruction to wait. Once the
|
|
451
|
+
ledger validates and `claim-verify` exits 0, retry the same request ID: the
|
|
452
|
+
refusal published no byte, so the retry is an ordinary no-clobber create and
|
|
453
|
+
takes the next number. The fence does not trap recovery — `claim-verify` stays
|
|
454
|
+
read-only, `claim-adopt` keeps its explicit override, claim release stays
|
|
455
|
+
available under barriers, and Git synchronization was never this tool's to
|
|
456
|
+
block.
|
|
457
|
+
|
|
458
|
+
**Recovery from an interrupted create.** An intent with no terminal resolves on
|
|
459
|
+
the next invocation from the item path alone: the candidate revision present
|
|
460
|
+
appends the committed terminal, the path absent appends the create abort, and
|
|
461
|
+
any third revision reports `legacy-mutation-outcome-unknown` as a global
|
|
462
|
+
barrier. Recovery never guesses a number and never publishes an item. Because
|
|
463
|
+
capacity for the intent, one clock entry, and one terminal is reserved before
|
|
464
|
+
the intent is appended, an exhausted journal refuses unchanged before
|
|
465
|
+
publication rather than stranding an unresolvable attempt.
|
|
466
|
+
|
|
467
|
+
**Create owns its item and its reconciliation log.** A successful create is
|
|
468
|
+
journal-visible from the item's birth, so its `changed_paths` and its
|
|
469
|
+
`--auto-commit` commit set are exactly the created item and
|
|
470
|
+
`<ledger>/.wowbagger/reconcile-<namespace>.md`. Owning the log it writes is not
|
|
471
|
+
permission to absorb residue: a reconciliation log already dirty before the
|
|
472
|
+
invocation is foreign, and create still refuses it.
|
|
473
|
+
|
|
474
|
+
**Commit every create before the next mutation.** A create has no authorized
|
|
475
|
+
predecessor — there is no earlier revision of an item that did not exist — so
|
|
476
|
+
Git `HEAD` is the only surface that can hold its authorized bytes, and an
|
|
477
|
+
uncommitted create reports the global `git-finalization-required` until it is
|
|
478
|
+
committed. A `patch` or `transition` may instead occupy the documented
|
|
479
|
+
authorized predecessor/successor window and run before its predecessor is
|
|
480
|
+
committed, but that window is an accepted overlap, not a license: commit every
|
|
481
|
+
mutation before the next one. Batch create is permanently rejected for the
|
|
482
|
+
direct-Markdown architecture. The supported bulk pattern is serial
|
|
483
|
+
`create --auto-commit`, one invocation and one commit per successful item; see
|
|
484
|
+
the [accepted batch-create decision](design/2026-08-30-batch-create.md).
|
|
485
|
+
|
|
486
|
+
**Cost.** A provisioned create already took the namespace lock, replayed the
|
|
487
|
+
journal, loaded the ledger, read Git `HEAD`, and reconciled. The fence changes
|
|
488
|
+
which findings refuse; it adds no new Git roster or history traversal to a
|
|
489
|
+
clean create, and the measured cost of the change is two extra fsync'd journal
|
|
490
|
+
appends. The durable cost is journal growth: each successful create adds one
|
|
491
|
+
intent and one committed terminal, so with no other activity the
|
|
492
|
+
65,536-entry limit permits at most 21,845 three-entry create cycles and the
|
|
493
|
+
8 MiB byte limit may bind first. Other claim and mutation activity lowers that
|
|
494
|
+
ceiling. Capacity is checked before publication and fails closed with exit 6
|
|
495
|
+
`claim-store-unavailable`, reason `journal-capacity-exceeded`, and `unchanged`;
|
|
496
|
+
no current-operation intent, terminal, or item byte is published. The same
|
|
497
|
+
reason applies to claim lifecycle, verification, adoption, publication reads,
|
|
498
|
+
and every legacy mutation while capacity is known before publication. A
|
|
499
|
+
genuine persistence failure remains `clock-floor-persistence-failed`, and an
|
|
500
|
+
ambiguous outcome after an intent remains an outcome-unknown refusal. Journal
|
|
501
|
+
history is never truncated, and automatic compaction is not part of this
|
|
502
|
+
contract.
|
|
503
|
+
|
|
504
|
+
A ledger that already carries duplicate numbers is item #182 recovery work and
|
|
505
|
+
is repaired through the separate `ledger-repair` contract, not through claim
|
|
506
|
+
operations. Run `number-repair-proposal --ledger <dir> --json`, review the
|
|
507
|
+
complete mapping, then run `number-repair --ledger <dir> --input <repair.json>
|
|
508
|
+
--json`. The repair command runs only when duplicate-number errors are the
|
|
509
|
+
complete validation failure, preserves ULID identities and relation values, and
|
|
510
|
+
publishes all affected items under the shared namespace fence. Arbitrary hand
|
|
511
|
+
edits remain unsupported because they can damage IDs, paths, or references.
|
|
512
|
+
|
|
513
|
+
**What alpha.14 guarantees, and what it does not.** Alpha.14 closes the
|
|
514
|
+
reported PropertyCompass2 collision: cooperating alpha.14 worktrees of one
|
|
515
|
+
clone that share one Git common directory can no longer commit two items
|
|
516
|
+
carrying the same number, because every such create is visible through the
|
|
517
|
+
shared journal before publication even without branch integration. Separate
|
|
518
|
+
clones, separate machines, alpha.13 writers before the hard cutover, and
|
|
519
|
+
noncooperating writes stay outside that fence and still rely on branch
|
|
520
|
+
integration plus `validate`.
|
|
521
|
+
|
|
522
|
+
**The upgrade is a hard cutover.** Alpha.13 cannot read a `create-v1` legacy
|
|
523
|
+
intent. Executed against a ledger whose shared journal already carries one, its
|
|
524
|
+
create exits 6 with `error.code` `claim-store-unavailable`, message
|
|
525
|
+
`The durable claim store is unavailable.`, and `error.details.reason`
|
|
526
|
+
`claim-store-unreadable`, leaving the state unchanged and writing no item. That
|
|
527
|
+
fail-closed behavior is the compatibility guarantee: an old writer cannot
|
|
528
|
+
commit another duplicate. It is also the operational limit, because alpha.13 is
|
|
529
|
+
immutable and emits a generic unreadable-store message rather than upgrade
|
|
530
|
+
guidance. Read that exact refusal as **this repository was written by a newer
|
|
531
|
+
Wowbagger; upgrade this worktree to continue.** There is no automatic migration
|
|
532
|
+
and no mixed-version grace period: upgrade every writer in one Git coordination
|
|
533
|
+
domain before the first alpha.14 create. If a partial upgrade writes the new
|
|
534
|
+
grammar first, every remaining alpha.13 worktree stops making claim-protected
|
|
535
|
+
mutations until it is upgraded. Item #185 owns the general ability of a running
|
|
536
|
+
old binary to diagnose its own version drift; no change here can retrofit a
|
|
537
|
+
message into an already-published executable.
|
|
348
538
|
|
|
349
539
|
**`unauthorized-revision` has two remedies, and only one of them is
|
|
350
540
|
destructive.** Restoring the authorized revision discards the out-of-protocol
|
|
@@ -359,26 +549,158 @@ Each refusal carries a `reason` that separates the cases:
|
|
|
359
549
|
| `reason` | Cause | Remedy |
|
|
360
550
|
|---|---|---|
|
|
361
551
|
| `git-finalization-required` | this worktree wrote the item and has not committed it | commit here, then `claim-verify` |
|
|
362
|
-
| `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,
|
|
552
|
+
| `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) |
|
|
363
553
|
| `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) |
|
|
364
554
|
|
|
555
|
+
**Scope is the coordinator's judgement, not a published member.** What a
|
|
556
|
+
finding refuses — every write, only a write against the item it names, or
|
|
557
|
+
nothing — is decided from the topology and travels beside the finding, never
|
|
558
|
+
inside it. No response carries it. A consumer MUST NOT infer scope from the
|
|
559
|
+
`reason` string, from the `remediation` sentence, or from whether `owner_ref`
|
|
560
|
+
is present: those are diagnostics for a person, and reading them as a scope
|
|
561
|
+
signal is exactly how alpha.11 treated an out-of-protocol revision as advisory
|
|
562
|
+
synchronization. The only supported scope signal is the refusal itself: a
|
|
563
|
+
mutation that returns exit 6 was blocked, and one that returns exit 0 was not.
|
|
564
|
+
|
|
565
|
+
Ownership on the current symbolic ref is never reported as a foreign worktree
|
|
566
|
+
owner, and a detached `HEAD` applies the same guard through its reachable
|
|
567
|
+
commit history. If the expected revision is already reachable from the current
|
|
568
|
+
checkout, a same-branch regression cannot be repaired by waiting for another
|
|
569
|
+
worktree; it remains `unauthorized-revision` and blocks unrelated mutations.
|
|
570
|
+
This distinguishes a real sibling's uncommitted successor from history the
|
|
571
|
+
current checkout already owns.
|
|
572
|
+
|
|
573
|
+
**`owner_ref` names an active named worktree, and nothing else.** Owner
|
|
574
|
+
evidence is the worktree roster Git reports live, searched this checkout first,
|
|
575
|
+
then every live worktree that has a branch checked out, ordered by branch ref
|
|
576
|
+
and then by path so one revision carried by several live worktrees always names
|
|
577
|
+
the same owner. A bare record, a record Git marks prunable, and a worktree
|
|
578
|
+
whose `HEAD` names no commit are excluded. `owner_ref` is always the branch of
|
|
579
|
+
one of those live worktrees, and `owner_commit` is the commit in that
|
|
580
|
+
worktree's history whose item bytes are the expected revision.
|
|
581
|
+
|
|
582
|
+
Reachability alone is never ownership. A tag, a remote-tracking ref, a branch
|
|
583
|
+
no worktree has checked out, and a live worktree on a detached `HEAD` can each
|
|
584
|
+
reach the expected revision while naming nobody who could publish it. All of
|
|
585
|
+
them, and a revision no reachable commit carries at all, produce
|
|
586
|
+
`owner_unavailable: true` and no `owner_ref`. So `owner_unavailable` means one
|
|
587
|
+
of three things: the expected revision is not reachable at all, a live sibling
|
|
588
|
+
holds it on a detached `HEAD`, or it is reachable only from refs no active
|
|
589
|
+
worktree has checked out.
|
|
590
|
+
|
|
591
|
+
Three `owner_unavailable` remediation sentences separate those cases. The
|
|
592
|
+
sentence naming ownership that cannot be established from reachable refs is
|
|
593
|
+
emitted only when this checkout has no item path and `HEAD` has none either, so
|
|
594
|
+
the item has never existed here. A revision Git does reach — from a tag, a
|
|
595
|
+
remote-tracking ref, a branch no worktree has checked out, or a live worktree on
|
|
596
|
+
a detached `HEAD` — gets the sentence saying the revision is reachable in Git
|
|
597
|
+
while no active named worktree owner is established. Its remedy is to inspect
|
|
598
|
+
the reachable history and restore or explicitly adopt reviewed bytes, then run
|
|
599
|
+
`claim-verify`; there is no commit left to wait for. Only a revision no
|
|
600
|
+
reachable commit carries at all gets the not-yet-reachable sentence, whose
|
|
601
|
+
remedy is to wait for the owning worktree to commit, then synchronize this
|
|
602
|
+
checkout and run `claim-verify`.
|
|
603
|
+
|
|
604
|
+
Through alpha.13 the reachable cases were given the not-yet-reachable sentence
|
|
605
|
+
too, which told a reader to wait for a commit Git already had. The wait never
|
|
606
|
+
ended, because no named worktree was ever going to publish those bytes. The
|
|
607
|
+
`reason`, the codes, the scope, and the finding members are unchanged; only the
|
|
608
|
+
`remediation` text for the reachable-unowned cases is corrected.
|
|
609
|
+
|
|
610
|
+
**Writer identity.** A legacy mutation and a claimed publication record the
|
|
611
|
+
worktree that authorized them in their journal entries as
|
|
612
|
+
`writer_worktree_id`: `legacy-mutation-intent`, `legacy-mutation`,
|
|
613
|
+
`publish-intent`, and `publish-final`. The field is optional on all of them: an
|
|
614
|
+
entry written before the field existed, which is every entry alpha.12 and
|
|
615
|
+
earlier wrote, stays valid, attributes nothing, and leaves the expected writer
|
|
616
|
+
`unknown`. Writer attribution alone therefore cannot turn such a finding into a
|
|
617
|
+
global barrier: it stays advisory `worktree-synchronization-required`. Local
|
|
618
|
+
state and current-checkout ownership are still judged first, and both remain
|
|
619
|
+
global barriers whatever the entry records. A publication resolves its identity
|
|
620
|
+
once under the claim lock, so its intent and its terminal name one writer, and
|
|
621
|
+
a terminal that recovery reconstructs after a lost response carries the
|
|
622
|
+
identity its intent recorded. When the journal names the current worktree as
|
|
623
|
+
the writer of the expected revision and no reachable ref carries that revision,
|
|
624
|
+
no sibling can ever produce it, so the finding is
|
|
625
|
+
`unauthorized-revision` and blocks every mutation instead of
|
|
626
|
+
`worktree-synchronization-required`, which blocks only its own item.
|
|
627
|
+
|
|
628
|
+
Recording the field on publication entries is an additive optional journal
|
|
629
|
+
change, so core `contract_version` stays `5` and every public request, success
|
|
630
|
+
envelope, and refusal envelope is unchanged. The item #122 lock-coarsening
|
|
631
|
+
parity golden, `test/publication-parity-baseline.json`, was regenerated to
|
|
632
|
+
record it: that recording is a work-claim contract change, never routine
|
|
633
|
+
fixture maintenance.
|
|
634
|
+
|
|
635
|
+
**What that identity discloses.** The identity is an opaque random UUID,
|
|
636
|
+
created once per worktree in that worktree's private Git directory. It contains
|
|
637
|
+
no path, branch, ref, hostname, user, or machine data. Journal entries are
|
|
638
|
+
projected into the tracked reconciliation log, so the identity persists in
|
|
639
|
+
committed Git history: any reader of that history can correlate every entry
|
|
640
|
+
written from one worktree, and an identity stays in that history indefinitely
|
|
641
|
+
after the worktree it named is removed. It never becomes a claim owner, and no
|
|
642
|
+
protocol decision reads it other than the writer comparison above.
|
|
643
|
+
|
|
644
|
+
**Ambiguous identity.** A UUID names one worktree. Before it reasons from a
|
|
645
|
+
recorded writer, the coordinator enumerates the worktrees Git currently reports
|
|
646
|
+
live and reads the identity each one already holds; it creates none. Two live
|
|
647
|
+
worktrees answering to one UUID, or a roster the coordinator could not finish
|
|
648
|
+
reading, refuses `claim-verify`, every claim-protected mutation,
|
|
649
|
+
`publish-claimed`, and `claim-adopt` before anything is classified or written.
|
|
650
|
+
The refusal keeps the existing exit `6`, `claim-store-unavailable`,
|
|
651
|
+
`claim-store-unreadable`, `state: "unchanged"` form and adds one
|
|
652
|
+
`error.details.identity_diagnostic`: `code: "duplicate-worktree-identity"` with
|
|
653
|
+
`worktree_id` and `live_worktree_count`, or `code:
|
|
654
|
+
"worktree-enumeration-failed"` with no further member. Auto-commit surfaces the
|
|
655
|
+
same diagnostic inside its `auto-commit-preflight-failed` details with
|
|
656
|
+
`retryable: false`. Nothing else about the envelope changes, and core
|
|
657
|
+
`contract_version` stays `5`.
|
|
658
|
+
|
|
659
|
+
Detection covers only what Git reports live. A worktree Git marks prunable —
|
|
660
|
+
including one whose path is temporarily unavailable or unmounted — is excluded,
|
|
661
|
+
so a duplicate identity held there is not detected until Git sees that worktree
|
|
662
|
+
live again. Removing a worktree removes the private Git directory that held its
|
|
663
|
+
identity, and nothing restores it: a worktree recreated at the same path earns
|
|
664
|
+
a new UUID, and the removed UUID stays in journal history attributing nothing.
|
|
665
|
+
|
|
365
666
|
### 3.2 Recovering from a foreign-writer block
|
|
366
667
|
|
|
367
668
|
1. Stop writing the affected item in the blocked worktree. Unrelated item
|
|
368
669
|
mutations may proceed when the finding is only
|
|
369
670
|
`worktree-synchronization-required`.
|
|
671
|
+
|
|
370
672
|
2. Read the finding. It names the item path and expected revision. When Git can
|
|
371
673
|
prove ownership, it also names `owner_ref` and `owner_commit`; otherwise it
|
|
372
674
|
carries `owner_unavailable: true`.
|
|
373
675
|
3. If an owner is named, WAIT for that owner to publish the commit, then
|
|
374
676
|
synchronize this checkout to it. Do not merge unrelated live work.
|
|
375
|
-
4. If ownership is unavailable,
|
|
376
|
-
the
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
677
|
+
4. If ownership is unavailable, the `remediation` string separates three cases.
|
|
678
|
+
When it says the expected revision is not yet reachable, the owning
|
|
679
|
+
worktree has not committed it yet: wait as in step 3, then synchronize.
|
|
680
|
+
When it says the revision is reachable in Git while no active named worktree
|
|
681
|
+
owner is established, do not wait: inspect the reachable history that carries
|
|
682
|
+
it, then restore the exact authorized bytes or use explicit `claim-adopt`
|
|
683
|
+
authority after review. When it says ownership cannot be established from
|
|
684
|
+
reachable refs, inspect reachable or dangling commits and remedy it the same
|
|
685
|
+
way. In neither inspection case do you copy peer working-tree bytes.
|
|
686
|
+
5. Run `claim-verify --ledger <dir> --id <finding.item_id> --json` in the
|
|
687
|
+
blocked worktree and require exit 0.
|
|
380
688
|
6. Resume the affected item.
|
|
381
689
|
|
|
690
|
+
Auto-commit applies this target scope both before and after its Git commit. A
|
|
691
|
+
successful mutation requires a valid ledger and no findings blocking the target
|
|
692
|
+
item, not a globally empty findings list. Nonblocking findings remain visible
|
|
693
|
+
in targeted verification.
|
|
694
|
+
|
|
695
|
+
**A target-scoped success is not a globally clean claim store.** The two scopes
|
|
696
|
+
answer different questions and a caller MUST NOT substitute one for the other.
|
|
697
|
+
`claim-verify --id <item>` keeps every finding visible, marks each with
|
|
698
|
+
`blocks_verification_scope`, and derives its exit from that item plus global
|
|
699
|
+
barriers. Bare `claim-verify` remains strict repository diagnosis and exits 6
|
|
700
|
+
while any item carries a blocking finding. A cooperating worker can therefore
|
|
701
|
+
finish its own item while bare verification still reports sibling work. Do not
|
|
702
|
+
hand-edit or adopt a sibling's item to force either scope clean.
|
|
703
|
+
|
|
382
704
|
`claim-verify` in the writing worktree finalizes that worktree's own state; it
|
|
383
705
|
does not make a sibling checkout able to see the item. Synchronization or
|
|
384
706
|
explicit recovery is still required for the affected item.
|
|
@@ -420,10 +742,12 @@ answering with `namespace: "work-claim"`, `command: "claim-adopt"`, and
|
|
|
420
742
|
mutation runs and no item file changes. It is not a `claim-verify` flag, because
|
|
421
743
|
`claim-verify` is the read-mostly reconciliation report every remediation string
|
|
422
744
|
names, and one command name must not mean both "tell me the state" and "change
|
|
423
|
-
the authorization baseline". It is not a `claim` subcommand, because
|
|
424
|
-
|
|
425
|
-
reconciliation reports blocking
|
|
426
|
-
that state, so it runs while
|
|
745
|
+
the authorization baseline". It is not a `claim` subcommand, because
|
|
746
|
+
`work-claim.acquire` and `work-claim.renew` refuse with
|
|
747
|
+
`publication-reconciliation-required` while reconciliation reports blocking
|
|
748
|
+
findings, and adoption exists to clear exactly that state, so it runs while
|
|
749
|
+
those findings stand. `work-claim.release` also runs while they stand, for the
|
|
750
|
+
narrower reason that surrendering a lease takes no authority (section 5).
|
|
427
751
|
|
|
428
752
|
The request is UTF-8 JSON with exactly these members:
|
|
429
753
|
|
|
@@ -556,6 +880,23 @@ fails or its durability is uncertain, the backend returns exit 6 with
|
|
|
556
880
|
to guess. It cannot report a lease success or semantic rejection whose time
|
|
557
881
|
was not persisted.
|
|
558
882
|
|
|
883
|
+
**A refused reconciliation has already advanced the floor.** Reconciliation
|
|
884
|
+
persists its clock entry before it classifies anything, so a command that then
|
|
885
|
+
refuses with exit 6 `claim-store-unavailable`, reason
|
|
886
|
+
`publication-reconciliation-required`, has still moved the durable floor to
|
|
887
|
+
`max(physical_utc, previous_floor)`. No claim, epoch, or ledger byte changes,
|
|
888
|
+
and the refusal reports none. The floor is monotonic, so the effect is on time
|
|
889
|
+
alone and it is permanent: once a refused reconciliation has published a floor
|
|
890
|
+
at or past a lease's `expires_at`, that lease can never read live again, and a
|
|
891
|
+
later acquire, renew, or fence check on the same tuple observes it expired.
|
|
892
|
+
On a ledger whose floor already runs ahead of this caller's wall clock, every
|
|
893
|
+
refused reconciliation republishes the higher floor, so a holder whose lease
|
|
894
|
+
looks live by its own clock may find it expired the moment it asks. A caller
|
|
895
|
+
that meets a barrier during a long recovery SHOULD expect to reacquire rather
|
|
896
|
+
than assume its lease survived. This is section 5's rule for a rejected
|
|
897
|
+
decision applied one step earlier: an advanced floor is never evidence that
|
|
898
|
+
anything succeeded.
|
|
899
|
+
|
|
559
900
|
When `last_epoch` is `18446744073709551615` (the unsigned 64-bit maximum), a
|
|
560
901
|
new acquire or takeover is impossible. After the authoritative decision time
|
|
561
902
|
has been persisted, the backend returns exit 6 `epoch-exhausted` with message
|
|
@@ -570,6 +911,22 @@ member at any depth, and exactly the listed members. Unknown members, wrong
|
|
|
570
911
|
types, noncanonical values, and unprovisioned namespaces are exit 2
|
|
571
912
|
`invalid-request`; no authoritative lease decision has then occurred.
|
|
572
913
|
|
|
914
|
+
**A reconciliation barrier stops a caller from taking or extending authority,
|
|
915
|
+
never from surrendering it.** `work-claim.acquire` and `work-claim.renew` MUST
|
|
916
|
+
refuse with exit 6 `claim-store-unavailable`, reason
|
|
917
|
+
`publication-reconciliation-required`, and the reconciliation `findings`,
|
|
918
|
+
whenever reconciliation reports a finding that blocks the request's item.
|
|
919
|
+
`work-claim.release` MUST NOT refuse for that reason. The holder has to be able
|
|
920
|
+
to hand the lease back: no other worktree can take the item over while the
|
|
921
|
+
claim is held, and the worktree holding it is often the one least able to clear
|
|
922
|
+
the barrier. Refusing the surrender strands the lease and the item with it.
|
|
923
|
+
|
|
924
|
+
Release keeps every other refusal. An identity the domain cannot resolve, a
|
|
925
|
+
journal it cannot read, and a clock floor it cannot persist all refuse before
|
|
926
|
+
any lease decision, and the owner, epoch, and expected-expiry CAS tuple below
|
|
927
|
+
still rules on the request: a mismatch is exit 4 `claim-conflict` and the claim
|
|
928
|
+
stays held. A barrier never turns a release into an unconditional discard.
|
|
929
|
+
|
|
573
930
|
### Read
|
|
574
931
|
|
|
575
932
|
`work-claim.read` accepts exactly:
|
|
@@ -641,6 +998,10 @@ It uses the same precedence. Success sets `active` to `null`, retains
|
|
|
641
998
|
`last_epoch`, and returns `released_claim` plus `read_back`. A later acquire
|
|
642
999
|
must allocate a greater epoch, preventing ABA even across restart.
|
|
643
1000
|
|
|
1001
|
+
A reconciliation barrier does not refuse a release; it refuses only acquire and
|
|
1002
|
+
renew (section 5 preamble). The CAS tuple still applies, so a release under a
|
|
1003
|
+
barrier is exactly as conditional as a release without one.
|
|
1004
|
+
|
|
644
1005
|
Success envelopes for these three commands have exactly `ok`, `namespace`,
|
|
645
1006
|
`command`, `contract_version`, `state: "committed"`, and `result`. Semantic
|
|
646
1007
|
failures replace `result` with `error`, use `state: "unchanged"`, and include
|
|
@@ -805,18 +1166,21 @@ recovers an owner only when the OS reports the PID absent.
|
|
|
805
1166
|
|
|
806
1167
|
That reconciliation is unconditional. It is not conditioned on an unresolved
|
|
807
1168
|
`publish-intent`, because the commit-per-mutation invariant (section 1) binds
|
|
808
|
-
`publish-claimed` exactly as it binds `create`, `transition`,
|
|
809
|
-
an uncommitted legacy mutation leaves no
|
|
810
|
-
reconciliation produces any blocking finding,
|
|
811
|
-
with exit 6 `claim-store-unavailable`,
|
|
1169
|
+
`publish-claimed` exactly as it binds `create`, `transition`, `parent-migrate`,
|
|
1170
|
+
`snooze`, and `patch`, and an uncommitted legacy mutation leaves no
|
|
1171
|
+
publish-intent behind. When reconciliation produces any blocking finding,
|
|
1172
|
+
`publish-claimed` MUST refuse with exit 6 `claim-store-unavailable`,
|
|
812
1173
|
`details.reason: "publication-reconciliation-required"`, and
|
|
813
1174
|
`details.findings` set to those findings. `state` MUST be `unchanged`: the
|
|
814
1175
|
refused publication wrote no item byte. Reconciliation itself still writes —
|
|
815
1176
|
a clock entry, the terminals it resolved, and the finalizations it observed —
|
|
816
|
-
because those record what was already true, never a new item revision.
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
1177
|
+
because those record what was already true, never a new item revision.
|
|
1178
|
+
|
|
1179
|
+
This is the identical refusal section 7 defines for a legacy write. The
|
|
1180
|
+
authorized predecessor/successor window from section 1 produces no blocking
|
|
1181
|
+
finding, so not every uncommitted mutation refuses the next command. When
|
|
1182
|
+
blocking findings do exist, `claim-verify` is the one reconciliation procedure
|
|
1183
|
+
for all of them.
|
|
820
1184
|
|
|
821
1185
|
That refusal also outranks step 5. The numbered precedence orders a backend
|
|
822
1186
|
that judges a candidate against an authoritative ledger; a merge-coordinated
|
|
@@ -839,13 +1203,14 @@ process MAY recover the lock only when the operating system reports that owner
|
|
|
839
1203
|
process as absent. A live or malformed lock remains `claim-store-unavailable`;
|
|
840
1204
|
elapsed time alone never authorizes lock recovery.
|
|
841
1205
|
|
|
842
|
-
`claim-verify` takes the ledger path
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
1206
|
+
`claim-verify` takes the ledger path, optional target item ID, and no request
|
|
1207
|
+
body. The target ID is syntax-validated without requiring local item presence.
|
|
1208
|
+
Under the namespace lock, it replays the journal, advances and persists the
|
|
1209
|
+
clock floor, and reconciles pending intents against exact item revisions. It
|
|
1210
|
+
also compares successful publications with Git `HEAD`. When `HEAD` contains a
|
|
1211
|
+
committed revision, it appends one idempotent `publish-finalization` entry that
|
|
1212
|
+
records the Git commit. It writes a per-namespace reconciliation log outside
|
|
1213
|
+
the shared journal; that log is a derived audit artifact, not authority.
|
|
849
1214
|
|
|
850
1215
|
The log projects only journal entries that record a decision. `clock` and
|
|
851
1216
|
`publish-finalization` entries are never projected. A command therefore writes
|
|
@@ -880,13 +1245,19 @@ not a claim finding, and a caller MUST NOT treat it as one. It is the honest
|
|
|
880
1245
|
statement that a clean claim answer is not a clear road, and the caller must
|
|
881
1246
|
repair validation before the next mutation will run.
|
|
882
1247
|
|
|
883
|
-
A
|
|
884
|
-
`
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
`state: "
|
|
888
|
-
|
|
889
|
-
|
|
1248
|
+
A verification result always names `verification_scope`, and every finding
|
|
1249
|
+
states `blocks_verification_scope`. Bare mode treats every blocking repository
|
|
1250
|
+
finding as blocking. Target mode keeps unrelated target-scoped findings
|
|
1251
|
+
visible but nonblocking; findings classified as global still block. A clean
|
|
1252
|
+
scope returns exit 0 and `state: "committed"`. This state describes durable
|
|
1253
|
+
coordinator reconciliation, not Git finalization of every successful
|
|
1254
|
+
publication; callers must inspect each `result.publications` row's
|
|
1255
|
+
`git_finalized` and `git_commit`. Findings named `pending-intent-resolved` are
|
|
1256
|
+
visible and nonblocking. Any blocking `legacy-mutation-outcome-unknown`,
|
|
1257
|
+
`publication-outcome-unknown`, `revision-regression`, or
|
|
1258
|
+
`stale-write-detected` finding returns exit 6 and `state: "unknown"`. A caller
|
|
1259
|
+
MUST stop publication work and inspect those findings. Repeating verification
|
|
1260
|
+
MUST NOT duplicate a publication finalization.
|
|
890
1261
|
|
|
891
1262
|
Every finding that blocks a mutation MUST carry a `remediation` string, and
|
|
892
1263
|
that string MUST name both the action to take and `claim-verify`. It MUST also
|
|
@@ -948,9 +1319,9 @@ without a second ledger write.
|
|
|
948
1319
|
|
|
949
1320
|
Every refusal in this section answers in the `ledger-mutation` namespace with
|
|
950
1321
|
`contract_version: 1` and `command: "<core command>-v1"`, even though the caller
|
|
951
|
-
invoked the core `create`, `transition`, or `patch`
|
|
952
|
-
version 1 consumer surface; it is not the core
|
|
953
|
-
re-wrapped in one.
|
|
1322
|
+
invoked the core `create`, `transition`, `parent-migrate`, `snooze`, or `patch`
|
|
1323
|
+
command. This is a pinned version 1 consumer surface; it is not the core
|
|
1324
|
+
mutation envelope and MUST NOT be re-wrapped in one.
|
|
954
1325
|
|
|
955
1326
|
For a fenced or merge-coordinated capability, legacy transition MUST check the
|
|
956
1327
|
active claim under the same namespace lock and return exit 4
|
|
@@ -960,18 +1331,24 @@ create MUST reject any item identity whose tuple has claim history with exit 4
|
|
|
960
1331
|
high-water mark. Both checks persist authoritative decision time before their
|
|
961
1332
|
response.
|
|
962
1333
|
|
|
963
|
-
Before a merge-coordinated backend permits a legacy transition or
|
|
964
|
-
write bytes, it MUST fsync a `legacy-mutation-intent`
|
|
965
|
-
candidate revisions. Under the same namespace lock, it MUST
|
|
966
|
-
capacity for that intent, one recovery clock entry, and one
|
|
967
|
-
committed write appends `legacy-mutation`; an unchanged write
|
|
968
|
-
`legacy-mutation-abort`. Reconciliation resolves a pending intent to
|
|
969
|
-
committed terminal when the candidate revision is present, or to the abort
|
|
1334
|
+
Before a merge-coordinated backend permits a legacy create, transition, or
|
|
1335
|
+
patch to write bytes, it MUST fsync a `legacy-mutation-intent` carrying the
|
|
1336
|
+
expected and candidate revisions. Under the same namespace lock, it MUST
|
|
1337
|
+
reserve journal capacity for that intent, one recovery clock entry, and one
|
|
1338
|
+
terminal entry. A committed write appends `legacy-mutation`; an unchanged write
|
|
1339
|
+
appends `legacy-mutation-abort`. Reconciliation resolves a pending intent to
|
|
1340
|
+
the committed terminal when the candidate revision is present, or to the abort
|
|
970
1341
|
terminal when the expected revision remains. Any third revision produces
|
|
971
1342
|
`legacy-mutation-outcome-unknown`. The latest committed terminal is the
|
|
972
1343
|
authorized expected revision. A later unrecorded revision remains a stale
|
|
973
1344
|
write.
|
|
974
1345
|
|
|
1346
|
+
A create has no predecessor, so its intent carries `expected_revision: null`,
|
|
1347
|
+
which is valid only for `create-v1`, and the item path's absence is what
|
|
1348
|
+
resolves it to the abort terminal. Section 3.1 states the complete create
|
|
1349
|
+
ordering, its allocation fence, and the alpha.13 cutover the widened grammar
|
|
1350
|
+
forces.
|
|
1351
|
+
|
|
975
1352
|
A merge-coordinated backend MUST reconcile before it authorizes a legacy write,
|
|
976
1353
|
and section 6 states the same requirement for `publish-claimed`.
|
|
977
1354
|
When reconciliation produces any blocking finding, the legacy command MUST
|
|
@@ -1086,9 +1463,10 @@ merge-coordinated backend returns when reconciliation found blocking findings,
|
|
|
1086
1463
|
and it is the reason an uncommitted prior mutation produces. **`claim-verify`
|
|
1087
1464
|
is its reconciliation procedure.** The envelope also carries
|
|
1088
1465
|
`details.findings`; act on each finding's `remediation` string, then run
|
|
1089
|
-
`wowbagger claim-verify --ledger <dir> --json`
|
|
1090
|
-
|
|
1091
|
-
|
|
1466
|
+
`wowbagger claim-verify --ledger <dir> --id <finding.item_id> --json` for the
|
|
1467
|
+
affected item and require exit 0 before repeating that item's refused command.
|
|
1468
|
+
Use bare verification only for strict repository diagnosis. Nothing else
|
|
1469
|
+
reconciles the journal, and no other verb is needed.
|
|
1092
1470
|
|
|
1093
1471
|
This code was added after the version 1 vectors were written. It is additive:
|
|
1094
1472
|
it names a condition the original text did not model, changes no existing code,
|