wedge(analyze): remediate C1/I1/I2/U1/U2 — rebuild-by-partition-switch, contracts synced to as-built, runbook
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
c52f968f38
commit
9ec83bf7f2
4 changed files with 88 additions and 3 deletions
|
|
@ -16,9 +16,13 @@ per-surface code.
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
import httpx
|
||||
|
||||
PARTITION = "market:catalog"
|
||||
# Overridable so a full catalog rebuild can land in a fresh partition and readers
|
||||
# switch by config (constitution II rebuildability; see docs/runbook.md).
|
||||
PARTITION = os.environ.get("M2_MARKET_CATALOG_PARTITION", "market:catalog")
|
||||
|
||||
|
||||
class CatalogClient:
|
||||
|
|
|
|||
60
docs/runbook.md
Normal file
60
docs/runbook.md
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
# m2-market operational runbook
|
||||
|
||||
## Catalog rebuild (constitution II rebuildability)
|
||||
|
||||
The live memory-api is store/search only — no delete, no partition drop — so
|
||||
`m2-market-indexer` reindex is an **idempotent fill** of the current partition, not a
|
||||
rebuild. A true rebuild (needed after listing updates/delists or index corruption) is a
|
||||
**fresh-partition switch**:
|
||||
|
||||
```bash
|
||||
# 1. Fill a fresh partition from the registry (source of truth)
|
||||
MEMORY_API_PARTITION=market:catalog-v2 \
|
||||
MEMORY_API_URL=... MEMORY_API_KEY=... FORGEJO_URL=... FORGEJO_TOKEN=... \
|
||||
LISTING_SCHEMA_PATH=$REPO/schemas/listing.schema.json \
|
||||
python3 -m m2_market_indexer.reindex
|
||||
|
||||
# 2. Switch every reader (CLI/Store/Scout) by config — no code change
|
||||
export M2_MARKET_CATALOG_PARTITION=market:catalog-v2 # or config management
|
||||
|
||||
# 3. The old partition is abandoned in place (memory-api cannot delete it);
|
||||
# it costs storage only. Fold the suffix rotation back when memory-api
|
||||
# grows delete support (fedlearn T13 follow-up).
|
||||
```
|
||||
|
||||
Verification: `m2-market search <known listing>` returns the same results against the
|
||||
new partition (quickstart.md Scenario E).
|
||||
|
||||
## memory-api endpoint discovery
|
||||
|
||||
The live memory-api container IP changes across Coolify redeploys (known split-brain,
|
||||
two stacks). Resolve the current one:
|
||||
|
||||
```bash
|
||||
MC=$(docker ps --format '{{.Names}}' | grep memory-api | head -1)
|
||||
docker inspect $MC | jq -r '.[0].NetworkSettings.Networks | to_entries[0].value.IPAddress'
|
||||
```
|
||||
|
||||
Public route: `https://memory.machinemachine.ai`. Writes need `X-API-Key` (the key lives
|
||||
in the memory-api container env as MEMORY_API_KEY); reads are currently open.
|
||||
|
||||
## Ledger
|
||||
|
||||
- Public: `https://ledger.machinemachine.ai` (Cloudflare TLS → Traefik http router — the
|
||||
domain is registered `http://` in Coolify on purpose; re-adding `https://` recreates
|
||||
the redirect loop).
|
||||
- Deploy: push to `m2/m2-market` master → Forgejo webhook (hook id 14) → Coolify deploy.
|
||||
Webhook delivery can lag ~40 s. Manual: `GET /api/v1/deploy?uuid=<app>` on Coolify.
|
||||
- Keys: `/home/m2/.m2-ledger-keys` (host, 0600). Service key reads/installs; admin key
|
||||
grants/snapshots.
|
||||
- Daily snapshot: host cron 03:10 → `POST /snapshot` → commits
|
||||
`m2/market-registry:audit/YYYY-MM-DD.json`. Balance disputes: recompute from the tx
|
||||
log (`GET /tx`), which is append-only truth.
|
||||
- Refund semantics: compensating refunds may overdraw a seller (debt position, settled
|
||||
at manual payout reconciliation) — data-model.md §Transaction.
|
||||
|
||||
## Publish/status flow (known gap, analyze finding I2)
|
||||
|
||||
`m2-market publish` opens the PR with `status: "draft"`; nothing flips it on merge yet.
|
||||
Until automated: after merging a listing PR, commit `status: "published"` on main (the
|
||||
indexer only indexes published listings).
|
||||
|
|
@ -39,3 +39,22 @@ Response items: `{listing_id, score, listing: <payload>}` ordered by hybrid scor
|
|||
Install + proposal events land as memory records in `market:evidence` partition keyed by
|
||||
listing_id; a periodic indexer pass folds counts into `stats` in the registry (PR-less
|
||||
stats commit or scheduled batch PR — implementation's choice, registry remains truth).
|
||||
|
||||
## AS-BUILT AMENDMENT (2026-07-02, analyze finding I1)
|
||||
|
||||
The live memory-api (agent.memory.system) exposes `/memory/store` + `/memory/search`
|
||||
only — no partition drop, no delete, no upsert-by-id, and its server-side tenant filter
|
||||
is a scalar `tenant_id` (not an array match). The implementation therefore deviates:
|
||||
|
||||
- Partition = the `agent_id` namespace; documents are memories with the listing in
|
||||
`metadata.listing` and `metadata.listing_id`.
|
||||
- Writer upsert = search-first for `metadata.listing_id`, store only if absent
|
||||
(idempotent fill). Listing UPDATES and delist removal require memory-api delete —
|
||||
until then, delisted/updated listings are filtered/handled at read time.
|
||||
- Reader tenant filter `tenant_visibility ⊇ {caller tenant | "*"}` is enforced in the
|
||||
ONE shared reader client (`cli/src/m2_market/catalog.py`), which every surface uses.
|
||||
- Full rebuild = fresh-partition switch (`MEMORY_API_PARTITION` writer-side,
|
||||
`M2_MARKET_CATALOG_PARTITION` reader-side) — docs/runbook.md.
|
||||
|
||||
The original section above is retained as the target contract for when memory-api
|
||||
grows partition CRUD (fedlearn T13 follow-up).
|
||||
|
|
|
|||
|
|
@ -60,8 +60,10 @@ pytest ledger/tests/test_properties.py # balance==fold(log); grants a
|
|||
## Scenario E — catalog rebuildability (constitution II)
|
||||
|
||||
```bash
|
||||
m2-market reindex # drop + rebuild market:catalog
|
||||
m2-market search "branded pdf report" # same results as before reindex
|
||||
# memory-api has no delete: rebuild = fresh-partition switch (docs/runbook.md)
|
||||
MEMORY_API_PARTITION=market:catalog-e2e python3 -m m2_market_indexer.reindex
|
||||
M2_MARKET_CATALOG_PARTITION=market:catalog-e2e \
|
||||
m2-market search "branded pdf report" # same results as the live partition
|
||||
```
|
||||
|
||||
## Scenario F — M2 Store on canary (Story 4) [after row 8]
|
||||
|
|
|
|||
Loading…
Reference in a new issue