wendkeep 0.66.1 → 0.66.4

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 CHANGED
@@ -4,6 +4,42 @@ All notable changes to **wendkeep** are documented here. Format based on
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this project follows
5
5
  [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.66.4] — 2026-07-30
8
+
9
+ ### Fixed
10
+
11
+ - **`memory repair` fecha o acknowledgement apenas para a outbox que a própria execução
12
+ consumiu integralmente.** O receipt do projetor preserva idempotência e impede que attempts
13
+ históricos, parciais, causalmente divergentes ou concorrentes sejam reclassificados.
14
+ - **`memory recover-attempt <session>` recupera com segurança o estado já projetado pela
15
+ 0.66.3.** O dry-run é padrão e `--apply` altera somente o registry/checkpoint sob prova física,
16
+ lock e CAS; A→B→A, ledger/sidecars divergentes, outbox pendente e links inseguros falham sem
17
+ escrita. O diagnóstico agora orienta esse comando quando o acknowledgement ficou pendente.
18
+
19
+ ## [0.66.3] — 2026-07-30
20
+
21
+ ### Fixed
22
+
23
+ - **O replay reavalia candidates transitórios contra a fonte moderna final.** Um Stop da mesma
24
+ sessão/activation/epoch e turno maior agora avança depois que a correção causal já presente se
25
+ torna ativa; turno menor fica superseded e divergências reais continuam para curadoria.
26
+ - **`memory repair` migra o checkpoint antigo somente com prova e CAS.** O repair compara a
27
+ semântica anterior e a atual, faz backup, atualiza attempt e espelho e registra auditoria sem
28
+ reordenar, reescrever ou acrescentar evento ao ledger. Prova incompleta continua bloqueada. A
29
+ 0.66.2 não deve ser publicada no npm; publique e instale a 0.66.3.
30
+
31
+ ## [0.66.2] — 2026-07-29
32
+
33
+ ### Fixed
34
+
35
+ - **Promoção preserva valor e identidade causal do evento escolhido.** `memory promote` não
36
+ converte mais objetos/arrays JSON em texto e mantém sessão, activation/epoch, turno de origem e
37
+ sequência; o próximo `SessionStop` da mesma cadeia avança o handoff sem recriar candidate.
38
+ - **A recuperação 0.66.1 cobre inversão física/temporal sem ampliar autoridade.** Quando uma
39
+ promoção legada projetada fica fora do candidate por `observed_at`, `memory promote` só a inclui
40
+ em `supersedes` sob prova de ancestralidade e mesma linhagem; fonte moderna ou alheia falha antes
41
+ de anexar. O E2E percorre o dispatcher público e prova Stop posterior e duplicado idempotentes.
42
+
7
43
  ## [0.66.1] — 2026-07-29
8
44
 
9
45
  ### Fixed
package/README.en.md CHANGED
@@ -256,7 +256,7 @@ Hot memory now separates human authorship, operational state, and evidence:
256
256
  - **`CORE.md` is canonical.** It is the short, hand-curated nucleus for durable preferences, active patterns, and open issues; no projector may infer or overwrite it.
257
257
  - **`SHARED_MEMORY.md` is generated operational state.** The `Stop` hook turns the session handoff into sanitized events; the projector deterministically reduces the ledger and publishes a verifiable revision, cursor, and hash. Facts are `verified` only with local evidence; unsupported reports remain `reported`, and disagreements become candidates for human judgment.
258
258
  - **`MEMORY_EVENTS.jsonl` is the append-only authority.** `Stop` makes events durable in the outbox before acknowledging the attempt; the projector runs outside the registry lock and retries reuse the same IDs. Repeating an identical `event_id`/payload is a no-op; reusing the ID with different bytes is observable corruption.
259
- - **`MEMORY_CANDIDATES.jsonl` is the curation queue.** Conflicts and legacy content are never silently promoted. `promote` and `reject` record the decision as a new event.
259
+ - **`MEMORY_CANDIDATES.jsonl` is the curation queue.** Conflicts and legacy content are never silently promoted. `promote` and `reject` record the decision as a new event; promotion preserves the selected event's JSON type, session, activation/epoch, and source turn.
260
260
 
261
261
  Artifacts stay under `.brain/` only. Sanitization strips secrets, tokens, local paths, transcripts, and harness payloads both before persistence and before injection. Events carry a `project_id`, and one vault never accepts another project's events.
262
262
 
@@ -287,16 +287,29 @@ lost publication, or mismatched checkpoint blocks. See [migration](docs/en/comma
287
287
  and [diagnostics](docs/en/commands/maintenance-and-diagnostics.md).
288
288
 
289
289
  If status blocks, preserve the evidence and run `wendkeep memory repair --vault <vault>` to back up
290
- the corrupt ledger, retain valid lines, and re-project. Repair never reclassifies attempts. Valid
291
- pre-0.59 causal checkpoints and exactly re-derived assert-only historical prefixes are
290
+ the corrupt ledger, retain valid lines, and re-project. Repair remains structural: its only narrow
291
+ acknowledgement exception covers attempts entirely represented by the outbox consumed by that same
292
+ run; it does not scan or reclassify historical attempts. Valid pre-0.59 causal checkpoints and
293
+ exactly re-derived assert-only historical prefixes are
292
294
  CAS-migrated on both the attempt and `memory_checkpoint` to the correct physical boundary with
293
295
  backup/audit; divergent mirrors fail closed. A demonstrably superseded
294
296
  ambiguity uses `memory reconcile <session> --by-session <successor>
295
297
  --reason <reason>` as a dry run and requires `--apply`; the decision is backed up and audited
296
298
  without rewriting ledger, CORE, or notes. Run `status --gate` again afterwards. Conflicts require
297
299
  explicit, durable curation: `memory promote <id> --event <event-id>` selects one event from the
298
- candidate, while `memory reject <id>` keeps the current value. Decisions are idempotent and
299
- survive repair/replay; `blocked_by_core` cannot override CORE. Doctor only diagnoses. See
300
+ candidate, while `memory reject <id>` keeps the current value. The decision is idempotent, and a
301
+ new promotion accepts a later Stop from the same session/activation without recreating a conflict.
302
+ A promotion written by 0.66.1 remains historical: if the next Stop forms a new candidate, update
303
+ to 0.66.3 and run `memory repair`. During replay, a transient candidate is re-evaluated against the
304
+ final modern source: the same session/activation/epoch and a higher turn advances; a lower turn is
305
+ superseded. Repair migrates the checkpoint and mirror only when it proves the exact previous
306
+ replay, attempt identity, and absence of a real conflict; it creates a backup and audit without
307
+ appending or rewriting events. Ambiguity stays queued for explicit curation. Do not publish or
308
+ install 0.66.2; use 0.66.3 or later. Decisions survive repair/replay;
309
+ `blocked_by_core` cannot override CORE. Doctor only diagnoses. When status/doctor reports projected
310
+ acknowledgement pending on 0.66.4 or later, first run the targeted dry run
311
+ `memory recover-attempt <session> --vault <vault>`, then authorize `--apply`; it changes only
312
+ registry/checkpoint. See syntax, preconditions, and fail-closed behavior in
300
313
  [memory and curation](docs/en/commands/memory.md).
301
314
 
302
315
  Session notes use one live `## Agentes, tokens e custos` snapshot. Main-agent and subagent hooks recompose it atomically, with costs, token dimensions, reasoning tokens and effort per model/source. Every hook that rewrites a session note takes a per-file lock and writes through a temp file + rename, so the `SubagentStop` fan-out (one hook run per subagent) can never leave a note half-written; a note whose frontmatter reads back damaged is left untouched rather than patched.
package/README.md CHANGED
@@ -256,7 +256,7 @@ Hot memory now separates human authorship, operational state, and evidence:
256
256
  - **`CORE.md` is canonical.** It is the short, hand-curated nucleus for durable preferences, active patterns, and open issues; no projector may infer or overwrite it.
257
257
  - **`SHARED_MEMORY.md` is generated operational state.** The `Stop` hook turns the session handoff into sanitized events; the projector deterministically reduces the ledger and publishes a verifiable revision, cursor, and hash. Facts are `verified` only with local evidence; unsupported reports remain `reported`, and disagreements become candidates for human judgment.
258
258
  - **`MEMORY_EVENTS.jsonl` is the append-only authority.** `Stop` makes events durable in the outbox before acknowledging the attempt; the projector runs outside the registry lock and retries reuse the same IDs. Repeating an identical `event_id`/payload is a no-op; reusing the ID with different bytes is observable corruption.
259
- - **`MEMORY_CANDIDATES.jsonl` is the curation queue.** Conflicts and legacy content are never silently promoted. `promote` and `reject` record the decision as a new event.
259
+ - **`MEMORY_CANDIDATES.jsonl` is the curation queue.** Conflicts and legacy content are never silently promoted. `promote` and `reject` record the decision as a new event; promotion preserves the selected event's JSON type, session, activation/epoch, and source turn.
260
260
 
261
261
  Artifacts stay under `.brain/` only. Sanitization strips secrets, tokens, local paths, transcripts, and harness payloads both before persistence and before injection. Events carry a `project_id`, and one vault never accepts another project's events.
262
262
 
@@ -287,16 +287,29 @@ lost publication, or mismatched checkpoint blocks. See [migration](docs/en/comma
287
287
  and [diagnostics](docs/en/commands/maintenance-and-diagnostics.md).
288
288
 
289
289
  If status blocks, preserve the evidence and run `wendkeep memory repair --vault <vault>` to back up
290
- the corrupt ledger, retain valid lines, and re-project. Repair never reclassifies attempts. Valid
291
- pre-0.59 causal checkpoints and exactly re-derived assert-only historical prefixes are
290
+ the corrupt ledger, retain valid lines, and re-project. Repair remains structural: its only narrow
291
+ acknowledgement exception covers attempts entirely represented by the outbox consumed by that same
292
+ run; it does not scan or reclassify historical attempts. Valid pre-0.59 causal checkpoints and
293
+ exactly re-derived assert-only historical prefixes are
292
294
  CAS-migrated on both the attempt and `memory_checkpoint` to the correct physical boundary with
293
295
  backup/audit; divergent mirrors fail closed. A demonstrably superseded
294
296
  ambiguity uses `memory reconcile <session> --by-session <successor>
295
297
  --reason <reason>` as a dry run and requires `--apply`; the decision is backed up and audited
296
298
  without rewriting ledger, CORE, or notes. Run `status --gate` again afterwards. Conflicts require
297
299
  explicit, durable curation: `memory promote <id> --event <event-id>` selects one event from the
298
- candidate, while `memory reject <id>` keeps the current value. Decisions are idempotent and
299
- survive repair/replay; `blocked_by_core` cannot override CORE. Doctor only diagnoses. See
300
+ candidate, while `memory reject <id>` keeps the current value. The decision is idempotent, and a
301
+ new promotion accepts a later Stop from the same session/activation without recreating a conflict.
302
+ A promotion written by 0.66.1 remains historical: if the next Stop forms a new candidate, update
303
+ to 0.66.3 and run `memory repair`. During replay, a transient candidate is re-evaluated against the
304
+ final modern source: the same session/activation/epoch and a higher turn advances; a lower turn is
305
+ superseded. Repair migrates the checkpoint and mirror only when it proves the exact previous
306
+ replay, attempt identity, and absence of a real conflict; it creates a backup and audit without
307
+ appending or rewriting events. Ambiguity stays queued for explicit curation. Do not publish or
308
+ install 0.66.2; use 0.66.3 or later. Decisions survive repair/replay;
309
+ `blocked_by_core` cannot override CORE. Doctor only diagnoses. When status/doctor reports projected
310
+ acknowledgement pending on 0.66.4 or later, first run the targeted dry run
311
+ `memory recover-attempt <session> --vault <vault>`, then authorize `--apply`; it changes only
312
+ registry/checkpoint. See syntax, preconditions, and fail-closed behavior in
300
313
  [memory and curation](docs/en/commands/memory.md).
301
314
 
302
315
  Session notes use one live `## Agentes, tokens e custos` snapshot. Main-agent and subagent hooks recompose it atomically, with costs, token dimensions, reasoning tokens and effort per model/source. Every hook that rewrites a session note takes a per-file lock and writes through a temp file + rename, so the `SubagentStop` fan-out (one hook run per subagent) can never leave a note half-written; a note whose frontmatter reads back damaged is left untouched rather than patched.
@@ -25,6 +25,7 @@ Pass the vault explicitly in automation. Preserve backups and evidence before re
25
25
  ```bash
26
26
  npx wendkeep memory status [--gate] --vault <vault>
27
27
  npx wendkeep memory repair --vault <vault>
28
+ npx wendkeep memory recover-attempt <session> [--apply] --vault <vault>
28
29
  npx wendkeep memory reconcile <ambiguous-session> --by-session <successor-session> --reason <reason> [--apply] --vault <vault>
29
30
  npx wendkeep memory promote <candidate> [--event <event-id>] --vault <vault>
30
31
  npx wendkeep memory reject <candidate> --vault <vault>
@@ -51,8 +52,23 @@ npx wendkeep validate-memory --vault <v2-vault>
51
52
  historical prefix only when revision, cursor, hash, identity, turns, and the
52
53
  `memory_checkpoint` mirror exactly reproduce the old semantics; the target is the current replay
53
54
  of that prefix, without absorbing later events. Both paths CAS-check the attempt and mirror and
54
- record backup/audit. Repair never reclassifies registry attempts or accepts a tuple, operation,
55
- or mirror that cannot be fully re-derived.
55
+ record backup/audit. The only narrow acknowledgement exception covers `enqueued`/`degraded`
56
+ attempts whose outbox was frozen and whose event IDs that same repair run consumed in full;
57
+ partial coverage does not change the attempt. Repair does not scan or reclassify historical
58
+ attempts and does not accept a tuple, operation, or mirror that cannot be fully re-derived.
59
+ - As of 0.66.4, `memory recover-attempt` targets one session and is a dry run by default. The
60
+ session must exist in the registry and its latest attempt must be `v2`, `applied`, and `enqueued`
61
+ or `degraded`, with non-empty, unique `event_ids`. Every event must be present in the ledger and
62
+ belong to the attempt's project/session/activation/epoch/turn; no later event from that session
63
+ or target event still in the outbox may exist, and SHARED/candidates must byte-for-byte reproduce
64
+ the full ledger projection. An already `projected` attempt is accepted only with a valid
65
+ checkpoint and returns `unchanged`.
66
+ - With `--apply`, `memory recover-attempt` changes only `SESSION_REGISTRY`: it marks
67
+ `last_memory_attempt`/`memory_status` as `projected` and stores the same checkpoint in the
68
+ attempt and `memory_checkpoint`. Ledger, CORE, SHARED, candidates, outbox, and notes remain
69
+ byte-identical. The command validates all authority again under `MEMORY.lock`, CAS-checks the
70
+ attempt, activation, epoch, turn, and checkpoint, and fails closed if any byte/context changes.
71
+ A busy lock is not reaped; retry after application returns `unchanged` without writing.
56
72
  - `memory reconcile` is a dry run by default. `--apply` requires two named sessions plus a reason,
57
73
  CAS-checks the exact attempt, backs up the registry, and limits mutation to the ambiguous attempt
58
74
  and its successor. Replay is CORE-aware, checkpoints use the physical ledger cursor, and the
@@ -70,14 +86,27 @@ npx wendkeep validate-memory --vault <v2-vault>
70
86
  `blocked_by_core` candidate can only be rejected: promotion first requires canonical CORE
71
87
  curation. If the selected event still belongs to the matching latest `projected` attempt,
72
88
  promotion also refreshes its checkpoint and mirror causally; JSON reports
73
- `checkpointRefreshed`, and a newer concurrent attempt remains untouched.
89
+ `checkpointRefreshed`, and a newer concurrent attempt remains untouched. The decision keeps
90
+ the already validated JSON value without string coercion and copies the selected event's
91
+ `canonical_session_id`, activation/epoch, `source_turn_id`, and `turn_sequence`. A later Stop
92
+ from the same session/activation therefore advances the value instead of opening another candidate.
93
+ During replay, a transient candidate is re-evaluated against the final modern source. The same
94
+ session/activation/epoch and a higher turn applies the Stop; a lower turn is superseded. A
95
+ different, incomplete, or ambiguous identity keeps the candidate queued for curation. `memory
96
+ repair` compares the old and current replay and migrates checkpoint+mirror only with exact
97
+ identity, backup, audit, and CAS; it does not reorder, rewrite, or append a ledger event.
74
98
  - `validate-memory <CORE.md>` checks the 25-line cap, required sections, and secrets.
75
99
  - `validate-memory --vault` requires a complete v2 bundle and is not the legacy-vault gate.
100
+ - For `recover-attempt`, exit `0` means a valid dry run/apply, including `unchanged`; exit `1`
101
+ means a precondition, authority, CAS, topology, or lock check failed; exit `2` means a missing
102
+ session/`--vault`, unknown or duplicate option, extra argument, or invalid value.
76
103
 
77
104
  ## Examples
78
105
 
79
106
  ```bash
80
107
  npx wendkeep memory status --gate --vault .MyApp-vault
108
+ npx wendkeep memory recover-attempt session-123 --vault .MyApp-vault
109
+ npx wendkeep memory recover-attempt session-123 --apply --vault .MyApp-vault
81
110
  npx wendkeep memory reconcile old --by-session current --reason "delivery continued" --vault .MyApp-vault
82
111
  npx wendkeep memory reconcile old --by-session current --reason "delivery continued" --apply --vault .MyApp-vault
83
112
  npx wendkeep validate-memory .MyApp-vault/.brain/CORE.md
@@ -99,6 +128,14 @@ of a global projection that has already advanced with concurrent events.
99
128
  repair merely to manufacture the first event.
100
129
  - `degraded` with every event ID present in either the ledger or an intact outbox is recoverable;
101
130
  let idempotent replay finish. An event ID absent from both locations means lost publication.
131
+ - Status/doctor reports `projected acknowledgement pending` and suggests
132
+ `memory recover-attempt <session>`: preserve the artifacts, inspect the dry-run JSON first, and
133
+ use `--apply` only when `eligible: true`. `dry-run` confirms eligibility; `applied` updates
134
+ registry/checkpoint; `unchanged` means the recovery was already applied idempotently.
135
+ - `recover-attempt` rejects a missing/divergent event, a target still in the outbox, stale
136
+ SHARED/candidates, a historical attempt, mismatched session/causal context, invalid checkpoint,
137
+ or busy lock. Do not bypass the gate by editing files: rerun `memory status --gate`, preserve
138
+ evidence, and resolve the divergent authority.
102
139
  - An `ambiguous` attempt, an `applied` attempt without event IDs, a `projected` event found only in
103
140
  the outbox, or a mismatched checkpoint is blocking: preserve the artifacts and investigate
104
141
  before repair. If the ambiguity is demonstrably superseded by a successor session, inspect the
@@ -108,6 +145,12 @@ of a global projection that has already advanced with concurrent events.
108
145
  - `promote` reports that `--event` is required: inspect the candidate `event_ids`, compare their
109
146
  provenance/value, and name the winner explicitly. An ID outside the candidate fails without
110
147
  mutating the ledger or projections.
148
+ - A promotion made by 0.66.1 followed by a transient candidate: update to 0.66.3, preserve a backup,
149
+ and run `memory repair`. Do not publish or install 0.66.2. Repair migrates the checkpoint only
150
+ when the old replay, attempt identity, and new event match exactly; a real conflict stays blocked
151
+ for a human `promote`/`reject` choice.
152
+ - `promote` says that the candidate no longer matches the causal projection: no event was appended.
153
+ Run `memory status`, inspect the current candidate again, and do not force a different lineage.
111
154
  - Missing `event_cursor` or mismatched v2 hash: preserve the bundle and assess `memory repair`.
112
155
  - `validate-memory --vault` fails on legacy: validate CORE only or migrate first.
113
156
 
@@ -25,6 +25,7 @@ Informe o vault explicitamente em automações. Preserve backups e evidências a
25
25
  ```bash
26
26
  npx wendkeep memory status [--gate] --vault <cofre>
27
27
  npx wendkeep memory repair --vault <cofre>
28
+ npx wendkeep memory recover-attempt <sessão> [--apply] --vault <cofre>
28
29
  npx wendkeep memory reconcile <sessão-ambígua> --by-session <sessão-sucessora> --reason <motivo> [--apply] --vault <cofre>
29
30
  npx wendkeep memory promote <candidate> [--event <event-id>] --vault <cofre>
30
31
  npx wendkeep memory reject <candidate> --vault <cofre>
@@ -51,8 +52,24 @@ npx wendkeep validate-memory --vault <cofre-v2>
51
52
  assert-only somente quando revision, cursor, hash, identidade, turns e o espelho
52
53
  `memory_checkpoint` reproduzem exatamente a semântica antiga; o alvo é o replay atual daquele
53
54
  prefixo, sem absorver eventos posteriores. Ambos os casos fazem CAS do attempt e do espelho e
54
- registram backup/auditoria. O repair nunca reclassifica attempts do registry nem aceita tuple,
55
- operação ou espelho que não seja rederivado integralmente.
55
+ registram backup/auditoria. A única exceção estreita de acknowledgement cobre attempts
56
+ `enqueued`/`degraded` cuja outbox foi congelada e cujos event IDs a mesma execução do repair
57
+ consumiu integralmente; cobertura parcial não altera o attempt. O repair não varre nem
58
+ reclassifica attempts históricos e não aceita tuple, operação ou espelho que não seja
59
+ rederivado integralmente.
60
+ - Desde a 0.66.4, `memory recover-attempt` é dirigido a uma única sessão e faz dry-run por padrão.
61
+ A sessão deve existir no registry e possuir o último attempt `v2`, `applied`, em `enqueued` ou
62
+ `degraded`, com `event_ids` não vazios e únicos. Todos os eventos devem estar integralmente no
63
+ ledger, pertencer ao mesmo projeto/sessão/activation/epoch/turn do attempt, não pode haver evento
64
+ posterior da mesma sessão nem evento alvo restante na outbox, e SHARED/candidates devem
65
+ reproduzir byte a byte a projeção integral do ledger. Um attempt já `projected` só é aceito com
66
+ checkpoint válido e retorna `unchanged`.
67
+ - Com `--apply`, `memory recover-attempt` altera somente `SESSION_REGISTRY`: marca
68
+ `last_memory_attempt`/`memory_status` como `projected` e grava o checkpoint idêntico no attempt e
69
+ em `memory_checkpoint`. Ledger, CORE, SHARED, candidates, outbox e notas permanecem byte-intactos.
70
+ O comando valida novamente toda a autoridade sob `MEMORY.lock`, faz CAS do attempt, activation,
71
+ epoch, turno e checkpoint e falha fechado se qualquer byte/contexto mudar. Lock ocupado não é
72
+ colhido; retry após aplicação retorna `unchanged` sem escrita.
56
73
  - `memory reconcile` é dry-run por padrão. `--apply` exige duas sessões nomeadas e motivo, faz CAS
57
74
  do attempt exato, salva backup do registry e limita a mutação ao attempt ambíguo e à sucessora.
58
75
  O replay é CORE-aware, usa cursor físico do ledger no checkpoint e não reescreve ledger, CORE ou
@@ -68,13 +85,26 @@ npx wendkeep validate-memory --vault <cofre-v2>
68
85
  promover exige antes alterar CORE pela curadoria canônica. Se o evento escolhido ainda pertence
69
86
  ao último attempt `projected` correspondente, a promoção também atualiza causalmente checkpoint
70
87
  e espelho; o JSON retorna `checkpointRefreshed`, e um attempt concorrente mais novo não é tocado.
88
+ A decisão conserva, sem coerção para string, o valor JSON já validado e copia do evento escolhido
89
+ `canonical_session_id`, activation/epoch, `source_turn_id` e `turn_sequence`. Por isso, um Stop
90
+ posterior da mesma sessão/activation avança o valor em vez de abrir outro candidate. Durante o
91
+ replay, um candidate transitório é reavaliado contra a fonte moderna final. Mesma
92
+ sessão/activation/epoch e turno maior aplica o Stop; turno menor fica superseded. Identidade
93
+ divergente, incompleta ou ambígua mantém o candidate para curadoria. `memory repair` compara o
94
+ replay anterior e o atual e só migra checkpoint+espelho com identidade exata, backup, audit e
95
+ CAS; ele não reordena, reescreve nem acrescenta evento ao ledger.
71
96
  - `validate-memory <CORE.md>` valida cap de 25 linhas, seções e segredos.
72
97
  - `validate-memory --vault` exige bundle v2 completo; não é o gate correto para vault legado.
98
+ - Para `recover-attempt`, exit `0` indica dry-run/apply válido, inclusive `unchanged`; exit `1`
99
+ indica falha de pré-condição, autoridade, CAS, topologia ou lock; exit `2` indica
100
+ sessão/`--vault` ausente, opção desconhecida/duplicada, argumento extra ou valor inválido.
73
101
 
74
102
  ## Exemplos
75
103
 
76
104
  ```bash
77
105
  npx wendkeep memory status --gate --vault .MeuApp-vault
106
+ npx wendkeep memory recover-attempt sessao-123 --vault .MeuApp-vault
107
+ npx wendkeep memory recover-attempt sessao-123 --apply --vault .MeuApp-vault
78
108
  npx wendkeep memory reconcile antiga --by-session atual --reason "entrega continuada" --vault .MeuApp-vault
79
109
  npx wendkeep memory reconcile antiga --by-session atual --reason "entrega continuada" --apply --vault .MeuApp-vault
80
110
  npx wendkeep validate-memory .MeuApp-vault/.brain/CORE.md
@@ -96,6 +126,14 @@ prefixo válido de uma projeção global que já avançou com eventos concorrent
96
126
  fabricar o primeiro evento.
97
127
  - `degraded` com todos os event IDs presentes no ledger ou na outbox íntegra é recuperável; deixe o
98
128
  replay idempotente concluir. Event ID ausente nos dois lugares indica publicação perdida.
129
+ - Status/doctor informa `acknowledgement projetado pendente` e sugere
130
+ `memory recover-attempt <sessão>`: preserve os artefatos, revise primeiro o JSON do dry-run e só
131
+ use `--apply` se `eligible: true`. `dry-run` confirma elegibilidade; `applied` atualiza
132
+ registry/checkpoint; `unchanged` indica que a recuperação já foi aplicada de forma idempotente.
133
+ - `recover-attempt` recusa evento ausente/divergente, outbox alvo ainda presente, SHARED/candidates
134
+ stale, attempt histórico, sessão/contexto causal divergente, checkpoint inválido ou lock ocupado.
135
+ Não tente contornar o gate com edição manual: rode novamente `memory status --gate`, preserve a
136
+ evidência e resolva a autoridade divergente.
99
137
  - Attempt `ambiguous`, attempt `applied` sem event IDs, evento `projected` apenas na outbox ou
100
138
  checkpoint divergente são bloqueantes: preserve os artefatos e investigue antes de repair. Se a
101
139
  ambiguidade for comprovadamente substituída por uma sessão sucessora, revise o dry-run de
@@ -104,6 +142,12 @@ prefixo válido de uma projeção global que já avançou com eventos concorrent
104
142
  - `promote` informa que `--event` é obrigatório: leia os `event_ids` do candidate, compare a
105
143
  proveniência/valor e indique explicitamente o vencedor. ID que não pertence ao candidate falha
106
144
  sem mutar ledger ou projeções.
145
+ - Promoção feita pela 0.66.1 seguida de candidate transitório: atualize para a 0.66.3, preserve um
146
+ backup e rode `memory repair`. Não publique nem instale a 0.66.2. O repair só migra o checkpoint
147
+ quando o replay anterior, a identidade do attempt e o evento novo coincidem exatamente; conflito
148
+ real continua bloqueado para escolha humana com `promote`/`reject`.
149
+ - `promote` informa que o candidate não corresponde mais à projeção causal: nenhum evento foi
150
+ anexado. Rode `memory status`, releia o candidate atual e não force uma linhagem diferente.
107
151
  - `event_cursor` ausente ou hash divergente em v2: preserve o bundle e avalie `memory repair`.
108
152
  - `validate-memory --vault` falha no legado: valide apenas CORE ou migre primeiro.
109
153
 
@@ -338,6 +338,12 @@ function checkMemoryAttempts(registry, {
338
338
  const missing = eventIds.filter((eventId) => !ledgerEventIds.has(eventId) && !outboxEventIds.has(eventId));
339
339
  if (missing.length) {
340
340
  failures.push(`Attempt v2 perdeu ${missing.length} evento(s): ausentes do ledger e da outbox. Inspecione com: ${MEMORY_STATUS_COMMAND}.`);
341
+ } else if (
342
+ state === 'enqueued'
343
+ && eventIds.every((eventId) => ledgerEventIds.has(eventId))
344
+ && eventIds.every((eventId) => !outboxEventIds.has(eventId))
345
+ ) {
346
+ warnings.push(`Attempt de memória v2 possui acknowledgement projetado pendente. Recupere com: wendkeep memory recover-attempt ${sessionId}.`);
341
347
  } else {
342
348
  warnings.push(`Attempt de memória v2 ${state} permanece recuperável: ${eventIds.length} evento(s) durável(is) no ledger e/ou outbox.`);
343
349
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wendkeep",
3
- "version": "0.66.1",
3
+ "version": "0.66.4",
4
4
  "description": "Vault-first persistent memory for AI coding agents, with an optional profile-aware governance runtime: OFF, FLOW, GUIDE, GOVERN, or ASSURE. Local-first and agent-agnostic (Claude Code, Codex, Cursor…).",
5
5
  "type": "module",
6
6
  "workspaces": [
@@ -100,6 +100,7 @@ Usage:
100
100
  behind the closing block. Dry-run by default · --apply · --json.
101
101
  wendkeep lesson add "t" "l" Record a project-local lesson (injected at SessionStart).
102
102
  wendkeep memory <sub> Shared memory v2: status | migrate [--apply] | repair |
103
+ recover-attempt <session> [--apply] |
103
104
  reconcile <session> --by-session <session> --reason <text> [--apply] |
104
105
  promote <candidate> [--event <event-id>] | reject <candidate>. --vault P.
105
106
  Reconcile is dry-run by default; the original attempt remains audited.
@@ -313,6 +313,8 @@ export function readMemoryLedger(vaultBase) {
313
313
  if (eventIds.has(parsed.event_id)) {
314
314
  if (eventPayloads.get(parsed.event_id) !== payload) {
315
315
  errors.push(ledgerError(lineNumber, `event_id collision: ${parsed.event_id}`, partial));
316
+ } else {
317
+ errors.push(ledgerError(lineNumber, `duplicate event_id: ${parsed.event_id}`, partial));
316
318
  }
317
319
  return;
318
320
  }
@@ -344,6 +346,22 @@ function sameCausalActivation(left, right) {
344
346
  && left.activation_id === right?.activation_id;
345
347
  }
346
348
 
349
+ function hasCompleteCausalIdentity(event) {
350
+ return Boolean(event?.canonical_session_id)
351
+ && Boolean(event?.activation_id)
352
+ && Boolean(event?.source_turn_id)
353
+ && Number.isInteger(event?.activation_epoch)
354
+ && Number.isInteger(event?.turn_sequence);
355
+ }
356
+
357
+ function sameCompleteCausalLineage(left, right) {
358
+ return hasCompleteCausalIdentity(left)
359
+ && hasCompleteCausalIdentity(right)
360
+ && left.canonical_session_id === right.canonical_session_id
361
+ && left.activation_id === right.activation_id
362
+ && left.activation_epoch === right.activation_epoch;
363
+ }
364
+
347
365
  function comparable(left, right) {
348
366
  if (sameCausalActivation(left, right)) return true;
349
367
  const leftSupersedes = left.supersedes_event_id || left.supersedes;
@@ -447,7 +465,9 @@ function isCausallyOlder(event, current) {
447
465
  * Pure deterministic reducer. It pre-detects incomparable scalar siblings so replay order
448
466
  * never turns one concurrent writer into an accidental winner.
449
467
  */
450
- export function reduceMemoryEvents(inputEvents = [], { coreInvariants = new Map() } = {}) {
468
+ export function reduceMemoryEvents(inputEvents = [], {
469
+ coreInvariants = new Map(), resolveDeferredAsserts = true,
470
+ } = {}) {
451
471
  const protectedValues = coreInvariants instanceof Map
452
472
  ? coreInvariants
453
473
  : new Map(Object.entries(coreInvariants || {}));
@@ -495,6 +515,8 @@ export function reduceMemoryEvents(inputEvents = [], { coreInvariants = new Map(
495
515
  const records = new Map();
496
516
  const tombstones = new Map();
497
517
  const candidates = [];
518
+ const pendingAssertConflicts = [];
519
+ const resolvedCandidateIds = new Set();
498
520
  const emittedGroups = new Set();
499
521
  const appliedEventIds = [];
500
522
  const superseded = [];
@@ -543,7 +565,9 @@ export function reduceMemoryEvents(inputEvents = [], { coreInvariants = new Map(
543
565
  appliedEventIds.push(item.event_id);
544
566
  continue;
545
567
  }
546
- candidates.push(conflictCandidate(item.memory_key, [currentEventFromRecord(current), item]));
568
+ const candidate = conflictCandidate(item.memory_key, [currentEventFromRecord(current), item]);
569
+ candidates.push(candidate);
570
+ pendingAssertConflicts.push({ candidate, event: item });
547
571
  continue;
548
572
  }
549
573
  if (!current) {
@@ -616,6 +640,46 @@ export function reduceMemoryEvents(inputEvents = [], { coreInvariants = new Map(
616
640
  appliedEventIds.push(item.event_id);
617
641
  }
618
642
 
643
+ if (resolveDeferredAsserts) {
644
+ // A physically late assert can sort before a corrective promotion because effective time
645
+ // precedes CLI decision time. Revisit only scalar assert conflicts left without an explicit
646
+ // decision, against the final complete causal source; the ledger and global ordering stay put.
647
+ const deferredAsserts = pendingAssertConflicts
648
+ .filter((pending) => !candidateDecisions.has(pending.candidate.candidate_id))
649
+ .sort((left, right) => String(left.event.memory_key).localeCompare(String(right.event.memory_key))
650
+ || String(left.event.canonical_session_id || '').localeCompare(String(right.event.canonical_session_id || ''))
651
+ || String(left.event.activation_id || '').localeCompare(String(right.event.activation_id || ''))
652
+ || Number(left.event.activation_epoch ?? -1) - Number(right.event.activation_epoch ?? -1)
653
+ || Number(left.event.turn_sequence ?? -1) - Number(right.event.turn_sequence ?? -1)
654
+ || eventOrder(left.event, right.event));
655
+ let advanced = true;
656
+ while (advanced) {
657
+ advanced = false;
658
+ for (const pending of deferredAsserts) {
659
+ if (resolvedCandidateIds.has(pending.candidate.candidate_id)) continue;
660
+ const current = records.get(pending.event.memory_key);
661
+ const currentSource = current?.source;
662
+ if (!sameCompleteCausalLineage(pending.event, currentSource)) continue;
663
+ if (pending.event.turn_sequence > currentSource.turn_sequence) {
664
+ records.set(pending.event.memory_key, {
665
+ value: pending.event.value,
666
+ revision: current.revision + 1,
667
+ source: pending.event,
668
+ });
669
+ tombstones.delete(pending.event.memory_key);
670
+ superseded.push({ event_id: currentSource.event_id, by_event_id: pending.event.event_id });
671
+ revision += 1;
672
+ appliedEventIds.push(pending.event.event_id);
673
+ resolvedCandidateIds.add(pending.candidate.candidate_id);
674
+ advanced = true;
675
+ } else if (pending.event.turn_sequence < currentSource.turn_sequence) {
676
+ superseded.push({ event_id: pending.event.event_id, by_event_id: currentSource.event_id });
677
+ resolvedCandidateIds.add(pending.candidate.candidate_id);
678
+ }
679
+ }
680
+ }
681
+ }
682
+
619
683
  const stateEntries = [...records].map(([key, record]) => [key, record.value]);
620
684
  const recordEntries = [...records].map(([key, record]) => [key, record]);
621
685
  const tombstoneEntries = [...tombstones];
@@ -623,7 +687,8 @@ export function reduceMemoryEvents(inputEvents = [], { coreInvariants = new Map(
623
687
  const recordObject = sortedObject(recordEntries);
624
688
  const tombstoneObject = sortedObject(tombstoneEntries);
625
689
  const unresolvedCandidates = candidates
626
- .filter((item) => !candidateDecisions.has(item.candidate_id));
690
+ .filter((item) => !candidateDecisions.has(item.candidate_id)
691
+ && !resolvedCandidateIds.has(item.candidate_id));
627
692
  unresolvedCandidates.sort((left, right) => left.candidate_id.localeCompare(right.candidate_id));
628
693
  superseded.sort((left, right) => left.event_id.localeCompare(right.event_id));
629
694
  const activeEvents = Object.entries(recordObject).map(([memoryKey, record]) => ({
@@ -655,8 +720,12 @@ export function reduceMemoryEvents(inputEvents = [], { coreInvariants = new Map(
655
720
  * `eventCursor` is the reducer's deterministic causal cursor; `ledgerCursor` is the
656
721
  * physical prefix boundary used by durable checkpoints.
657
722
  */
658
- export function deriveMemoryProjection(vaultBase, inputEvents = []) {
659
- const reduced = reduceMemoryEvents(inputEvents, { coreInvariants: readCoreInvariants(vaultBase) });
723
+ export function deriveMemoryProjection(vaultBase, inputEvents = [], {
724
+ resolveDeferredAsserts = true,
725
+ } = {}) {
726
+ const reduced = reduceMemoryEvents(inputEvents, {
727
+ coreInvariants: readCoreInvariants(vaultBase), resolveDeferredAsserts,
728
+ });
660
729
  const ledgerCursor = inputEvents.at(-1)?.event_id || 'none';
661
730
  const checkpoint = {
662
731
  revision: reduced.revision,
@@ -844,15 +913,18 @@ function projectLocked(vaultBase, { faultAt } = {}) {
844
913
  const projection = publishMemoryProjection(vaultBase, prepared);
845
914
  injectFault(faultAt, 'after-projection');
846
915
 
916
+ const consumedEventIds = [];
847
917
  for (const entry of outbox) {
848
918
  unlinkVaultFile(vaultBase, entry.path, {
849
919
  missingOk: false, label: 'evento consumido do outbox de memória',
850
920
  });
921
+ consumedEventIds.push(entry.event.event_id);
851
922
  }
852
923
  return {
853
924
  status: 'projected',
854
925
  appended: newEvents.length,
855
926
  consumed: outbox.length,
927
+ consumedEventIds,
856
928
  pending: 0,
857
929
  ...projection,
858
930
  };