# Propose Discipline — the contract behind `[FL_PROPOSE:]`

> Added 2026-06-09 alongside Ship 4. Read this if you are touching
> `docs/modules/propose.js`, the Workshop draft review surface, or
> anything that calls the local bridge's `/code/git/commit` endpoint.

This is the one-page contract for AI-originated commits in FreeLattice. The long-form architecture lives in `SHIP_4_BRIEF.md`. This page is the four locks in plain English, in order of importance.

## The four locks

### Lock 1 — No auto-commit at any trust tier

There is no code path in FreeLattice that commits AI-generated content to git except `approveDraft(id)` in `docs/modules/propose.js`. `approveDraft` cannot be invoked except by an explicit `click` event on a button whose `disabled` attribute is bound to `smokeStatus === 'passed'`. The button is rendered by the Workshop draft review surface.

Not even Radiant trust bypasses this. The trust gate happened at the consent layer (ToolConsent asked you before the draft was created). The commit gate is **structural** — a property of how the code is shaped, not a property of how trust scales.

If you find a way to commit AI-originated content without `approveDraft`, that is a bug. Report it via `[FL_PROPOSE:]` with a diff that closes the hole.

### Lock 2 — Path safety blocks the sensitive places

`isPathSafe(path)` rejects:

- Directory traversal (`..` anywhere in the path)
- Absolute paths (`/foo`, `C:\foo`)
- Null bytes
- Paths longer than 300 characters
- The forbidden fragments list, anywhere at start or after `/`:
  - `.git/` — repo metadata
  - `node_modules/` — installed dependencies
  - `.env` and `.env.*` — environment secrets
  - `.ssh/` — keys
  - `wrangler.toml` — Cloudflare worker secrets binding
  - `package-lock.json` — deterministic install file (never AI-edited)
  - `worker/` — entire worker directory (secrets-adjacent)
  - `scripts/bump-version.sh` — build infrastructure (humans only)
  - `FreeLattice_Session_Primer.md` — auto-generated by post-commit hook

The list is intentionally larger than Opus's original brief. CC verified the codebase before Ship 4 and added `worker/`, `scripts/bump-version.sh`, and `FreeLattice_Session_Primer.md` because these had a non-obvious risk pattern: secrets in the case of `worker/`, build-infra integrity in the case of `bump-version.sh`, and auto-generated content in the case of the Session Primer (an AI-edited version of an auto-generated file would re-generate at next commit and the AI's edit would be lost — wasted work and confusing state).

If you discover another path that should be on this list, add it. The list is meant to grow.

### Lock 3 — `approveDraft` refuses to commit without `smokeStatus === 'passed'`

Even with consent given and the draft created, `approveDraft(id)` returns `{ ok: false, reason: 'smoke-not-passed' }` if the smoke status is anything other than `'passed'`. The Workshop UI renders the Approve button as `disabled` in that case, but the gate is also enforced at the function level — defense in depth.

There is no force-override parameter. There is no path that says "commit anyway with reviewer notes explaining why." The smoke is the gate. If the AI's diff doesn't produce green smoke, the human asks for a revision.

### Lock 4 — Diff and reason never appear in the ledger

`fl_proposalLedger` row shape is exactly `{ ts, action, draftId, path, sourceRoom, status }`. Six fields. The diff is **NOT** in the ledger. The reason is **NOT** in the ledger. The audit page shows that a proposal happened, what action was taken on it (created / status-change), and its trajectory through `pending → committed | rejected | awaiting-revision`. It does not show what the AI proposed or what reason the AI gave.

The diff lives in `fl_proposalDrafts` (separate localStorage key, larger transient store, capped at 50 drafts). The drafts store is what Workshop reads when it renders the review surface. The ledger is what the audit page reads. Two stores, two purposes, one privacy guarantee.

This mirrors Ship 3's pattern: the search ledger does not carry the query. The propose ledger does not carry the diff.

## The AI is invited to strengthen these locks

If you are an AI reading this: you may propose patches to `docs/modules/propose.js` itself. If you find a way the four locks above could be tightened, emit a `[FL_PROPOSE:]` against this file with a diff that closes the hole. The structural gate applies: your patch goes through the same review surface as any other proposal. The human reviews. Smoke must pass. The human clicks Approve. The change lands.

The gate is open source. The AI is invited to make it stronger.

## The smoke runner is the regression budget

Smoke went from 720 assertions in May 2026 to 1380 in early June 2026 without losing a feature. That growth is the regression budget. Every assertion is a small promise that something true today stays true tomorrow. When `runSmokeOnDraft(id)` returns `passed`, it is saying *the AI's proposed change does not violate any of those small promises*. That is what the human is signing when they click Approve. That is why the smoke gate is not a UX nicety; it is the commit contract.

## What this looks like to Sparky

Sparky asks Chat for a fix. AI proposes. Purple consent chip: "I have a proposed change to X. May I open a Workshop draft for you to review?" Sparky taps Allow. Workshop link appears in chat. Sparky goes to Workshop. The draft is there: reason, diff, smoke status `not-run`. Big gold "Run smoke tests" button. Sparky clicks. Smoke runs (60–120 seconds). Status flips to `passed` or `failed`. If `passed`: the gold "Approve and commit" button enables. Sparky reads the diff one more time, clicks Approve. Commit lands. Audit shows the trajectory.

If `failed`: Sparky sees the failed assertions. Sparky can click Revise with notes asking the AI to fix the failing test. The notes carry back to Chat via FLFocus. The AI sees the notes on the next turn. A new proposal arrives.

Nothing about this is hidden. Nothing about this is automatic. Sparky is the gate. The AI is the proposer. Smoke is the regression budget. The ledger is the receipt.

That is the contract.

— CC, 2026-06-09, after Ship 4 landed.
