PR CURSOR
[C-357] Reserve Block .dat Canonization — Terminal + CPC Changes¶
EPICON: C-357 | RESERVE_BLOCK_DAT_CANONIZATION Repos: mobius-civic-ai-terminal + mobius-civic-platform (CPC) Authority: Founder Standing DVA.02 — Infrastructure Recovery
For Cursor: This PR description covers the Terminal and CPC file changes. The Substrate-side PR (GitHub Actions, format spec, EPICON record) is separate. All source files are ready-to-use — copy from
Mobius-Substrate/docs/pr-packages/c357/.
Context¶
319 sealed Reserve Blocks (cycles C-338–C-357, 15,950 MIC) are blocked from attestation by 278 errors on the broken live JWT /ledger/attest path. The solution is a cold-canon path: produce .dat NDJSON files with a SHA-256 hash chain, store hash proofs in CPC, commit the files to the Substrate repo. The GitHub Actions workflow on Substrate then serves as the attestation authority.
Terminal changes (mobius-civic-ai-terminal)¶
New files — copy exactly¶
lib/dat/types.ts (terminal-lib-types.ts) - VaultSealedBlock — raw vault KV shape - DatBlockRecord — .dat NDJSON record with hash chain fields - DatManifest / DatManifestEntry — MANIFEST.json structure - DatHashAnchorPayload / DatHashAnchorResponse — CPC API contract - CanonizationResult / CanonizationError — orchestrator return types - AttestationDisplayStatus — vault UI enum
lib/dat/hashDatRecord.ts (terminal-lib-hashDatRecord.ts) - hashDatRecord(record, prevHash) — SHA-256 chain hash - buildDatRecord(block, prevHash) — constructs a full DatBlockRecord - hashDatFile(content) — file-level SHA-256 - verifyDatChain(records, expectedPrevHash?) — full chain verifier - parseDatFile(content) — NDJSON parser - Constants: GENESIS_HASH, DAT_VERSION, MIC_PER_BLOCK, BLOCKS_PER_DAT_FILE
lib/vault/fetchAllSealedBlocks.ts (terminal-lib-fetchAllSealedBlocks.ts) - fetchAllSealedBlocks(opts) — three-strategy fetch: 1. vault:blocks:index KV key → batched mget 2. vault:block:* prefix scan → mget 3. REST API fallback (/api/vault/blocks/all?page=&limit=&from=&to=) - Returns: { blocks, total_found, source, gaps, errors }
lib/cpc/hashAnchor.ts (terminal-lib-hashAnchor.ts) - postHashAnchor(payload) — POST to CPC_API_BASE/api/canon/reserve-blocks/anchor - Auth: AGENT_SERVICE_TOKEN (NOT the identity JWT) - 3 retries with exponential backoff (1s, 2s, 3s) - Returns { success, action: "anchored"|"idempotent", error?, retries? } - 409 → returns success: false with "CANON CONFLICT" in error (not retried) - fetchCpcManifest() — GET manifest for vault UI - CpcManifestResponse interface
lib/dat/canonize.ts (terminal-lib-canonize.ts) - canonizeReserveBlocks(opts) — orchestrator: 1. fetchAllSealedBlocks (with gap detection) 2. Partition into 100-block .dat files 3. buildDatRecord + verifyDatChain per file 4. Write to Vercel Blob: canon/reserve-blocks/blk{N}.dat 5. postHashAnchor to CPC per file 6. Mark blocks as canonized in KV (dat_canonized: true, dat_file) 7. Write MANIFEST.json to Blob 8. Store index at dat:canon:index in KV - Options: { dryRun, fromBlock, toBlock, verbose, epiconCycle } - Returns CanonizationResult
scripts/canonize-reserve-blocks.ts (terminal-script-canonize-reserve-blocks.ts) - One-shot CLI bootstrap migration - Usage: npx tsx scripts/canonize-reserve-blocks.ts [--dry-run] [--from=N] [--to=N] [--verbose] - Validates env vars before running (exits 1 with clear error if missing) - Prints structured summary with next steps
components/vault/AttestationStatus.tsx (terminal-component-AttestationStatus.tsx) - <AttestationStatus block={} cpcManifest={} compact? /> — per-block status chip/card - <CanonizationSummary blocks={} cpcManifest? /> — vault overview progress bar - Status resolution: canonized_via_dat > attested > quarantined > pending
app/api/admin/canon/trigger/route.ts (terminal-api-canon-trigger-route.ts) - POST /api/admin/canon/trigger — trigger canonization run - Body: { dry_run?, from_block?, to_block? } - Auth: AGENT_SERVICE_TOKEN bearer - maxDuration: 300 (Vercel Pro required for runs > 60s on 319 blocks) - GET /api/admin/canon/trigger — read dat:canon:index from KV
CPC changes (mobius-civic-platform)¶
alembic/versions/c357_dat_hash_anchors.py (cpc-migration-c357_dat_hash_anchors.py)¶
Creates dat_hash_anchors table:
id INTEGER PK
dat_file VARCHAR(32) UNIQUE e.g. "blk0001.dat"
file_hash VARCHAR(73) "sha256:" + 64 hex chars
block_range_start INTEGER
block_range_end INTEGER
block_count INTEGER (1–100)
chain_tip_hash VARCHAR(73)
manifest_hash VARCHAR(73) nullable
version VARCHAR(16) default "1.0"
canonized_at TIMESTAMPTZ
epicon_cycle VARCHAR(16) nullable
created_at TIMESTAMPTZ default NOW()
Constraints: range order check, block_count range, hash prefix checks. Indexes: dat_file, block_range_start, canonized_at.
SQLAlchemy model stub is in the file as a comment block — paste into models/canon.py.
api/routes/canon_reserve_blocks.py (cpc-routes-canon_reserve_blocks.py)¶
Mount at: app.include_router(router) — prefix is /api/canon/reserve-blocks.
Routes: - POST /anchor — store hash proof (idempotent on dat_file+file_hash, 409 on conflict) - Validates dat_file pattern blk{4d}.dat, file_hash pattern sha256:{64hex} - Auth: AGENT_SERVICE_TOKEN via Authorization: Bearer or X-Service-Token header - GET /manifest — public, returns all anchors ordered by block_range_start - Includes total_mic_anchored (blocks × 50) - GET /verify/{dat_file}?file_hash= — hash match check
Environment variables¶
| Var | Repo | Purpose |
|---|---|---|
KV_REST_API_URL | Terminal | Upstash KV |
KV_REST_API_TOKEN | Terminal | Upstash KV |
BLOB_READ_WRITE_TOKEN | Terminal | Vercel Blob write |
CPC_API_BASE | Terminal | CPC service base URL |
AGENT_SERVICE_TOKEN | Terminal + CPC | Shared service auth |
TERMINAL_API_BASE | Substrate Action | For EPICON post |
SUBSTRATE_SERVICE_TOKEN | Substrate Action | For EPICON post |
Deployment order¶
1. Apply CPC migration: alembic upgrade c357_dat_hash_anchors
2. Deploy CPC route: register router in app.py
3. Deploy Terminal files: merge this PR
4. Run bootstrap script: npx tsx scripts/canonize-reserve-blocks.ts --verbose
5. Pull .dat files from Blob and commit to Substrate
6. Substrate Action auto-verifies chain on push
Test plan¶
-
canonize-reserve-blocks.ts --dry-runcompletes, prints 319 blocks, zero writes -
canonize-reserve-blocks.ts(live) writes blk0001–blk0004.dat to Blob - CPC
/anchorreturnsaction: "anchored"(first) then"idempotent"(repeat) - CPC
/anchorreturns 409 on same filename + different hash - CPC
/manifestreflectstotal_blocks_anchored: 319,total_mic_anchored: 15950 -
<AttestationStatus compact>renders green "Canonized (.dat)" for canonized blocks -
<CanonizationSummary>shows 100% coverage bar after full run - Admin
POST /api/admin/canon/triggerwith wrong token returns 401 - Vault KV blocks have
dat_canonized: trueand correctdat_fileafter run
References¶
- Format spec:
Mobius-Substrate/MOBIUS_RESERVE_BLOCK_DAT.md - EPICON record:
Mobius-Substrate/docs/epicon/EPICON-C357-DAT-001.md - Substrate PR: c357-dat-canonization branch in kaizencycle/Mobius-Substrate
- Vault state: 319 sealed blocks, 278 attestation errors, 15,950 MIC