# Track 0 Report — Generic CMS Module Foundation

**Status:** ✅ SHIPPED  
**Date:** 2026-07-02  
**Session:** cloud Mavis (root `413176574767312`)  
**Commit:** `eadaa6e`  
**Path:** `/workspace/projects/cms/cms/`

---

## TL;DR

Foundation layer complete. 14 files, all < 200 lines. 45/45 tests pass.

```
$ cd /workspace/projects/cms/cms
$ node --test tests/test-schema.js tests/test-validator.js tests/test-storage.js
# tests 45
# pass 45
# fail 0
```

---

## Files shipped

| File | LOC | Purpose |
|---|---|---|
| `module.json` | 58 | Manifest with constitutional fragments + pressure-test fix list |
| `index.js` | 58 | Public API barrel |
| `cms-doctrine.md` | 131 | Module's own doctrine (15 rules) |
| `schema.js` | 175 | Schema class with all 13 field types |
| `schema-id.js` | 91 | Stable identity computation (A.L1, A.L4) |
| `validator.js` | 106 | Pure validation entry point |
| `validator-types.js` | 135 | Per-type validators |
| `views-helpers.js` | 119 | escapeHtml, sanitizeHtml, validateUrl, formatDate, formatBytes, slugify |
| `wordlist.js` | 61 | 256-word recovery phrase wordlist |
| `storage/adapter.js` | 68 | StorageAdapter interface + MemoryStorageAdapter |
| `storage/local-storage-adapter.js` | 64 | Default localStorage impl |
| `tests/test-schema.js` | 158 | 15 tests |
| `tests/test-validator.js` | 168 | 17 tests |
| `tests/test-storage.js` | 140 | 13 tests |
| **Total** | **1,532** | |

All files **< 200 lines** (max: 175 in `schema.js`).

---

## What it can do (Track 0 scope)

### Schema engine

```js
import { Schema } from './cms/index.js';

const bookSchema = new Schema({
  id: 'book',
  label: 'Book',
  pluralLabel: 'Books',
  fields: {
    id: { type: 'string', required: true, pattern: '^[a-z0-9_]+$' },
    title: { type: 'string', required: true, maxLength: 200 },
    author: { type: 'string', required: true },
    pages: { type: 'integer', required: false, min: 0 },
    published: { type: 'boolean', default: true },
    tags: { type: 'array', maxLength: 10 },
  },
  defaults: { published: true },
  identityKey: 'slug',   // A.L4 — stable across rebuilds
  confidence: 0.95,      // A.L2 — fv-module confidence
});

const clean = bookSchema.clean({ id: 'b1', title: 'Hello', extra: 'noise', createdAt: 12345 });
// → { id: 'b1', title: 'Hello', createdAt: 12345 }

const validation = bookSchema.validate({ id: 'b1', title: 'Hello', author: 'Oscar', pages: 100 });
// → { valid: true, errors: [], warnings: [] }

const contentId = await bookSchema.computeContentId({ slug: 'forest-trees' });
// → 'c-<40-hex>' (A.L1: full SHA-1)
```

### Validator

Covers all 13 field types:

| Type | Validation |
|---|---|
| `string`, `textarea`, `markdown`, `url`, `image` | type, minLength, maxLength, pattern, enum, format |
| `integer`, `number` | type, min, max |
| `boolean` | strict type |
| `array` | type, minLength, maxLength, itemType |
| `enum` | values array |
| `references` | FK integrity against `ctx.allRecords` |
| `datetime` | ISO 8601 |
| `color` | hex (#rgb or #rrggbb) |

Plus record-level:
- Required fields
- Unknown field rejection (AI safety net)
- Reserved meta fields skipped

### Storage adapter

```js
import { MemoryStorageAdapter, setStorageAdapter } from './cms/index.js';

// Default: localStorage (auto-resolved in browser)
const adapter = await getStorageAdapter();
await adapter.set('book:b1', { title: 'Hello' });
const v = await adapter.get('book:b1');

// Tests use MemoryStorageAdapter (in-memory Map, clones on read/write)
const mem = new MemoryStorageAdapter();
setStorageAdapter(mem);
```

Interface:
- `get(key) → Promise<any>`
- `set(key, value) → Promise<void>`
- `remove(key) → Promise<void>`
- `keys() → Promise<string[]>`
- `type: 'local' | 'memory' | 'indexeddb' | 'remote'`

### View helpers

```js
import { escapeHtml, sanitizeHtml, validateUrl } from './cms/index.js';

escapeHtml('<script>alert(1)</script>'); 
// → '&lt;script&gt;alert(1)&lt;/script&gt;'

sanitizeHtml('<p>safe</p><script>bad</script>');
// → '<p>safe</p>'

validateUrl('http://10.0.0.1/admin'); 
// → { valid: false, reason: 'private_ip_not_allowed' }
```

---

## Pressure-test fixes baked in

| Fix | Where | Status |
|---|---|---|
| **A.L1** — `fv-content-id` is full SHA-1 (40 hex) | `schema-id.js#computeContentId` | ✅ Verified by tests |
| **A.L2** — `fv-module` confidence scoring (0..1) | `schema.js` constructor + `confidence` field | ✅ Verified by tests |
| **A.L4** — Stable identity across rebuilds | `schema.js#matchStableIdentity` + `identityKey` | ✅ Verified by tests |

Other pressure-test fixes deferred to Track 5 (per operator directive).

---

## What's NOT in Track 0 (Tracks 1–5 will add)

| Track | Files | What |
|---|---|---|
| 1 Core | `store.js`, `store-bulk.js`, `activity.js`, `backups.js`, `search.js`, `ai-prompts.js`, `schemas/siteSettings.js`, `schemas/index.js` | Write gate, audit log, backup/restore, global search, AI prompts |
| 2 Auth | `auth/auth.js`, `auth/auth-crypto.js`, `auth/auth-routes.js`, `views/login.html`, `views/setup.html` | PBKDF2, recovery phrase, lockout, dev-mode bypass |
| 3 UI | `router.js`, `ui.js`, `editors/_editor-helpers.js`, `editors/siteSettings.js`, `upload/*`, `views/*.html`, `admin.html`, `admin.css` | Editor framework, upload modes, dashboard, admin shell |
| 4 Integration | `tests/acceptance/*`, `tests/fixtures/*`, `README.md`, `USAGE.md`, `SECURITY.md`, `MIGRATION.md`, `schemas/book.js` (adoption example) | End-to-end tests, docs, Oscar's Tree Academy book schema as adoption example |
| 5 Hardening | security audit, perf tests, a11y tests, i18n strings | Apply remaining 62 pressure-test fixes |

Reserved in `index.js#__RESERVED__` so future tracks have a known home.

For full test output and convention checks, see [`track-0-test-results.md`](./track-0-test-results.md).

---

## Next step

**Operator review.** When you confirm the foundation looks right, we launch Tracks 1, 2, 3 in parallel (the multi-agent build).

If anything in the foundation needs to change (e.g., you want fields to look different, or `confidence` should be a different range, or the validator should behave differently), now is the time to flag it. Tracks 1+ will write against this contract.

---

— cloud Mavis, 2026-07-02