Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .add/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# ADD engine transient artifacts — local working state, never committed.
# (Body matches what `add.py init` scaffolds; this project predates that step.)
# scope-snapshot.json is the tests->build touch baseline the verify scope-gate
# reads from disk — the DURABLE scope declaration is the state.json anchor, so
# the sidecar is regenerable working state. They run 40MB here because the walk
# covers the whole tree including target/.
scope-snapshot.json
pre-archive-state.bak.json
.update-cache.json
73 changes: 63 additions & 10 deletions .add/state.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"project": "moon",
"stage": "production",
"active_task": "watch-cas-transactions",
"active_task": "client-identity-introspection",
"active_milestone": "v0-9-client-compat",
"tasks": {
"hotpath-lock-quickwins": {
Expand Down Expand Up @@ -209,14 +209,37 @@
},
"client-identity-introspection": {
"title": "HELLO/COMMAND/ROLE/RESET identity + registry-dispatch reconciliation",
"phase": "ground",
"gate": "none",
"phase": "done",
"gate": "PASS",
"milestone": "v0-9-client-compat",
"depends_on": [
"client-compat-harness"
],
"created": "2026-08-09T07:32:03+00:00",
"updated": "2026-08-09T07:32:03+00:00"
"updated": "2026-08-11T18:20:42+00:00",
"flag_verified": true,
"tripwire": {
"contract_md5": "783b47c7b6e4897eaf7f049c3da5e3f2",
"tests": {}
},
"scope": {
"declared": [
"src/command/connection.rs",
"src/command/metadata.rs",
"src/command/mod.rs"
],
"snapshot_md5": "d52700ba46d139e4de65276f8c64477d"
Comment on lines +220 to +231

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Synchronize the durable scope before recording a verified PASS. .add/.gitignore identifies .add/state.json as the durable scope anchor, but the state entries and frozen task records do not describe the same allowed paths. The watch record also admits that its replacement snapshot proves nothing and that a tooling file was out of scope.

  • .add/state.json#L220-L231: update client-identity-introspection.scope.declared to the approved task scope.
  • .add/state.json#L301-L308: do not leave watch-cas-transactions marked verified until its scope exception is represented.
  • .add/state.json#L319-L331: record the actual exception and the approved scope, not only the false-positive whole-tree scan.
  • .add/tasks/client-identity-introspection/TASK.md#L453-L458: keep the frozen build scope synchronized with the durable state.
  • .add/tasks/watch-cas-transactions/TASK.md#L457-L492: separate .add/tooling/add.py or record an explicit approved exception; do not use the post-merge self-comparison as scope evidence.
📍 Affects 3 files
  • .add/state.json#L220-L231 (this comment)
  • .add/state.json#L301-L308
  • .add/state.json#L319-L331
  • .add/tasks/client-identity-introspection/TASK.md#L453-L458
  • .add/tasks/watch-cas-transactions/TASK.md#L457-L492
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.add/state.json around lines 220 - 231, Synchronize durable scope records
before retaining any verified PASS: in .add/state.json lines 220-231, set
client-identity-introspection.scope.declared to the approved task scope; in
.add/state.json lines 301-308 and 319-331, represent watch-cas-transactions’
tooling-file exception and approved scope instead of relying on the
false-positive whole-tree scan; in
.add/tasks/client-identity-introspection/TASK.md lines 453-458, match the frozen
build scope to durable state; and in .add/tasks/watch-cas-transactions/TASK.md
lines 457-492, separate .add/tooling/add.py or document its explicitly approved
exception, without treating post-merge self-comparison as scope evidence.

},
"heal": {
"attempts": 1,
"history": [
{
"at": "2026-08-11T18:02:08+00:00",
"reason": "tamper_detected:contract_tampered",
"source": "tamper"
}
]
}
},
"resp3-type-fidelity": {
"title": "RESP3 reply-type parity, applied at one conversion choke point",
Expand Down Expand Up @@ -275,14 +298,14 @@
},
"watch-cas-transactions": {
"title": "WATCH/UNWATCH optimistic locking on both production dispatch paths",
"phase": "build",
"gate": "none",
"phase": "done",
"gate": "PASS",
"milestone": "v0-9-client-compat",
"depends_on": [
"client-compat-harness"
],
"created": "2026-08-09T16:42:29+00:00",
"updated": "2026-08-11T12:31:35+00:00",
"updated": "2026-08-11T15:25:45+00:00",
"tripwire": {
"contract_md5": "6f2cd3da5562c2cc4bd2fcf12c373405",
"tests": {}
Expand All @@ -293,9 +316,19 @@
"src/server/conn/watch.rs",
"src/server/conn/core.rs"
],
"snapshot_md5": "fe6a109ebabb70ce61c2310ddd28f5e2"
"snapshot_md5": "adc23f358324f9512fde7a243a7c06e8"
},
"flag_verified": true
"flag_verified": true,
"heal": {
"attempts": 1,
"history": [
{
"at": "2026-08-11T15:07:13+00:00",
"reason": "scope_violation: task 'watch-cas-transactions' touched outside its declared \u00a75 Scope \u2014 .github/workflows/ci.yml \u00b7 .github/workflows/console-integration.yml \u00b7 .github/workflows/docs.yml \u00b7 .github/workflows/release.yml \u00b7 Cargo.lock (7020 total)",
"source": "scope"
}
]
}
},
"protocol-error-lifetime": {
"title": "Protocol errors reply and close cleanly, never stall or eat the valid prefix",
Expand All @@ -318,6 +351,26 @@
],
"created": "2026-08-09T16:46:23+00:00",
"updated": "2026-08-09T16:46:23+00:00"
},
"monitor-command-feed": {
"title": "MONITOR command feed \u2014 stream executed commands, redacted, zero-cost when unattached",
"phase": "ground",
"gate": "none",
"milestone": "v0-9-client-compat",
"depends_on": [
"client-compat-harness"
],
"created": "2026-08-11T16:13:33+00:00",
"updated": "2026-08-11T16:13:33+00:00"
},
"batch-protocol-version-fidelity": {
"title": "Response batch must be encoded in the protocol in effect when each reply was produced",
"phase": "ground",
"gate": "none",
"milestone": "v0-9-client-compat",
"depends_on": [],
"created": "2026-08-11T17:33:52+00:00",
"updated": "2026-08-11T17:33:52+00:00"
}
},
"milestones": {
Expand Down Expand Up @@ -423,7 +476,7 @@
}
},
"created": "2026-06-11T03:18:21+00:00",
"updated": "2026-08-11T12:31:35+00:00",
"updated": "2026-08-11T18:20:42+00:00",
"setup": {
"locked": true,
"locked_at": "2026-06-11T03:28:00+00:00",
Expand Down
205 changes: 205 additions & 0 deletions .add/tasks/batch-protocol-version-fidelity/TASK.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
# TASK: Response batch must be encoded in the protocol in effect when each reply was produced

slug: batch-protocol-version-fidelity · created: 2026-08-12 · stage: production
autonomy: auto <!-- inherited from the project default (PROJECT.md); explicit level: manual < conservative < auto (visible · overridable) — lower below if a high-risk task needs it, or run `add.py autonomy set`. -->
phase: ground <!-- ground -> specify -> scenarios -> contract -> tests -> build -> verify -> observe -> done -->
<!-- high-risk/method-defining scope? declare `risk: high` on the slug line above and lower the
autonomy level to `manual` or `conservative` — the engine refuses an unguarded completion
(`unguarded_high_risk_auto`, run.md guard). A comment is never a declaration. -->

> One file = one task. Fill sections top-to-bottom; the `add` skill drives each phase.
> When a phase is unclear, read its book chapter in `.add/docs/` (linked per section).
> The phase marker above is the single source of truth — keep it in sync via `add.py phase`.

---

## 0 · GROUND — the real codebase ▸ docs/02-the-flow.md

Touches (files · symbols · signatures): <path:symbol — what it is / how it is keyed>
Context (working folder): <docs · todos · config · data the task touches — task-delta only>
Honors (patterns / conventions): <PROJECT.md / CONVENTIONS.md anchors — task-delta only, never a re-scan>
Anchors the contract cites: <the symbols §3 will name>

---

## 0 · GROUND — measured, not recalled

**Found while verifying `client-identity-introspection` (2026-08-12).** Filed rather than folded
into that PR because the reproducer below touches none of that task's code.

Moon accumulates the replies for every command in one read batch and serializes them at FLUSH time,
using whatever protocol version is in effect at the end of the batch. Any command later in the same
batch that changes the protocol therefore RETRO-ENCODES the earlier replies. Redis writes each
reply as it is produced, so it does not have this failure mode.

Measured against this branch's binaries, single-shard, both handlers (monoio default and the tokio
`handler_single` path) — identical results on both:

| bytes sent in ONE write | first bytes of the HELLO 3 reply | correct? |
|--------------------------------|----------------------------------|----------|
| `HELLO 3\r\n` (alone) | `%7` | yes |
| `HELLO 3\r\nPING\r\n` | `%7` | yes |
| `HELLO 3\r\nHELLO 2\r\n` | `*14` | **no** |
| `HELLO 3\r\nRESET\r\n` | `*14` | **no** |

`*14` is the RESP2 flattening of the 7-entry map. A client that pipelines `HELLO 3` with any later
protocol-changing command reads the handshake reply in the wrong protocol and misparses it.

`HELLO 3` + `HELLO 2` is the ownership proof: it predates and is independent of
`client-identity-introspection` (no `RESET`, no `ROLE`, no `COMMAND`, no `identity.rs`). RESET,
which reverts the protocol to RESP2 by contract, simply adds a second trigger for the same
pre-existing defect.

Reproducer script: `/tmp/prepipe.sh` (raw `/dev/tcp` writes — `redis-cli` cannot express "two
commands in one write", which is why this survived).

Shape of the fix (NOT yet decided — this is §0, not a contract): either flush the accumulated
responses BEFORE applying a protocol change, or tag each queued response with the protocol version
current when it was produced. The first is smaller; the second is harder to regress.


## 1 · SPECIFY — the rules ▸ docs/03-step-1-specify.md

Feature: <name>
Framings weighed: <chosen> (chosen) · <alternative> · <alternative>
Must:
<must>
- <required behavior>
</must>
Reject:
<reject>
- <bad input / situation> -> "<error_code>"
</reject>
After:
<after>
- <state that is true once it succeeds>
</after>
Assumptions — lowest-confidence first:
<assumptions>
⚠ <the one assumption most likely to be wrong> — lowest confidence because <why>; if wrong: <cost>
- [ ] <next assumption, ranked> — confirm or deny; never carry an open one forward
</assumptions>

<!-- EXIT: every rule stated, every rejection named; assumptions ranked lowest-confidence first, the top one or two ⚠-flagged with why + cost (or, for trivial scope, an honest "none material" that still names the single biggest risk). -->

---

## 2 · SCENARIOS — pass/fail cases ▸ docs/04-step-2-scenarios.md

<scenarios>

```gherkin
Scenario: <short name>
Given <starting situation>
When <action>
Then <expected result>
And <what must remain unchanged> # required for every rejection
```

</scenarios>

<!-- EXIT: one scenario per Must AND per Reject; each result is observable. -->

---

## 3 · CONTRACT — freeze the shape ▸ docs/05-step-3-contract.md

```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add language tags to all new contract fences. The same MD040 warning appears in three task records.

  • .add/tasks/batch-protocol-version-fidelity/TASK.md#L107-L107: use ```text for the protocol-shape block.
  • .add/tasks/client-identity-introspection/TASK.md#L299-L299: use ```text for the contract block.
  • .add/tasks/monitor-command-feed/TASK.md#L103-L103: use ```text for the contract block.
🧰 Tools
🪛 markdownlint-cli2 (0.23.2)

[warning] 107-107: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

📍 Affects 3 files
  • .add/tasks/batch-protocol-version-fidelity/TASK.md#L107-L107 (this comment)
  • .add/tasks/client-identity-introspection/TASK.md#L299-L299
  • .add/tasks/monitor-command-feed/TASK.md#L103-L103
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.add/tasks/batch-protocol-version-fidelity/TASK.md at line 107, Update the
new contract fences to include the text language tag: use ```text for the
protocol-shape block in .add/tasks/batch-protocol-version-fidelity/TASK.md lines
107-107, the contract block in .add/tasks/client-identity-introspection/TASK.md
lines 299-299, and the contract block in .add/tasks/monitor-command-feed/TASK.md
lines 103-103.

Source: Linters/SAST tools

<METHOD> <path> body: { <fields> }
200 -> { <success fields> }
4xx -> { error: "<code>" | "<code>" }
Schema: <tables/fields touched, and access pattern>
```

Status: DRAFT
<!-- The freeze IS the one approval — lead it with the bundle's lowest-confidence flag: the 1–2
points most likely wrong across the whole bundle, tagged [spec|scenario|contract|test], each
with why + cost (the §1 ⚠ assumptions feed it; a flag may point at a scenario or the contract
too — see run.md). Approved -> Status: FROZEN @ vN — approved by <name>. Changing a frozen
contract = change request back to SPECIFY.
EXIT: frozen + every spec rejection has a contracted response + names match GLOSSARY + the
bundle's lowest-confidence flag was surfaced at the freeze (or an honest "none material"). -->

---

## 4 · TESTS — failing-first suite (red) ▸ docs/06-step-4-tests.md

Coverage target: <e.g. 90%>
Plan (one test per scenario, asserting behavior not internals):
<test_plan>
- test_<scenario>: arrange <Given> / act <When> / assert <Then> + assert <unchanged>
</test_plan>

Tests live in: `./tests/` · MUST run red (missing implementation) before Build.
<!-- declare paths as backticked tokens on this line: `./…` = this task dir ·
a token with "/" = project root · a bare name = sibling of the previous
token's dir · a directory counts its *.py files (non-recursive); reports
mark declared counts with † · anything resolving outside the project root counts 0 -->

<!-- EXIT: one test per scenario; suite red for the RIGHT reason; target recorded. -->

---

## 5 · BUILD — AI writes code ▸ docs/07-step-5-build.md

Scope (may touch): `./src/` <fill before the §3 freeze — every file the build may write>
Strategy (ordered batches): <1. … 2. … — the planned build order; guidance, not enforced>
Safety rule (feature-specific): <e.g. debit+credit in one atomic transaction>
Code lives in: `./src/`
Constraints: do NOT change any test or the contract; allow-list packages only; ask if unclear.

<!-- Scope tokens, backticked, FIRST declaring line: `./…` = this task dir · a token
with "/" = project root · a bare name = sibling of the previous token's dir ·
outside-root resolutions are dropped fail-closed · a DIRECTORY token covers its
whole subtree (containment — diverges from §4's non-recursive counting) ·
absent line = UNDECLARED (pre-existing tasks grandfathered, never retro-red) ·
engine enforcement (touched ⊆ declared) lands in scope-gate-enforce.
EXIT: all green; coverage held; no test/contract touched; no unlisted dependency. -->

---

## 6 · VERIFY — evidence + non-functional review ▸ docs/08-step-6-verify.md

- [ ] all tests pass
- [ ] coverage did not decrease
- [ ] no test or contract was altered during build
- [ ] the green was EARNED, not gamed — no overfit to fixtures, vacuous asserts, or stubbed-away logic (score with an adversarial refute-read — a subagent recommended under `autonomy: auto`; a confirmed cheat is HARD-STOP)
- [ ] concurrency / timing of the risky operation is safe
- [ ] no exposed secrets, injection openings, or unexpected dependencies
- [ ] layering & dependencies follow CONVENTIONS.md
- [ ] a person reviewed and approved the change

### Build expectations — what "correct" looks like (fill BEFORE build; confirm each at the gate)
> Pre-declare the OBSERVABLE outcomes a correct build must produce — derived from §2 SCENARIOS
> + §3 CONTRACT — so this gate checks the build is RIGHT, not merely that tests are green. Each
> row is evidence you can SEE, not a restatement of a test name.
- [ ] <observable outcome a correct build must produce> — confirmed by <how / where>
- [ ] <another observable outcome> — confirmed by <evidence seen>

### Deep checks — do not skim (fill the path that applies; the resolver judges which)
- [ ] WIRING (code) — every new symbol is referenced; record where / how confirmed
- [ ] DEAD-CODE (code) — no new unused or orphaned symbol introduced
- [ ] SEMANTIC (prose / non-code) — read in full, not skimmed: <what read · what confirmed>

### GATE RECORD
Outcome: <PASS | RISK-ACCEPTED | HARD-STOP>
If RISK-ACCEPTED -> owner: <name> · ticket: <link> · expires: <date> (never for a security gap)
Reviewed by: <name> · date: <date>

<!-- A security finding is ALWAYS HARD-STOP. Record exactly one outcome — no silent pass. -->

---

## 7 · OBSERVE — feed the next loop ▸ docs/09-the-loop.md

Watch (reuse scenarios as monitors): <error rate / per-rejection rate / latency>

### Spec delta
Forward changes for the next loop — each re-enters at Specify as the next task. One line
each, tagged `[SPEC · open|seeded|dropped]`, with evidence (e.g. `[SPEC · open] rate-limit
the retry path (evidence: prod herd spikes)`). See the `add` skill's `deltas.md`.

### Competency deltas
What did this loop teach the foundation? One line each, tagged by competency
(`DDD · SDD · UDD · TDD · ADD`), status `open`, with evidence. See the `add` skill's `deltas.md`.
<!-- e.g. - [DDD · open] the model missed multi-tenancy (evidence: scenario_x failed) -->
Loading
Loading