# MeshKore standard — changelog

The MeshKore standard is the plain-text protocol that describes the
`.meshkore/` folder layout, the cluster bootstrap, the agent docs
contract, and the daemon API surface. As the standard evolves, each
version gets a section here. Every running daemon checks
`https://meshkore.com/standard/version` periodically; when the local
`.meshkore/STANDARD_VERSION` falls behind, the operator (or the LLM
agent acting on their behalf) reads this changelog and applies the
catch-up manually.

The catch-up engine is the LLM. No declarative migration format. No
squash logic. Prose entries describe what changed and how to apply,
the LLM does the edit. This is on purpose — see
[`spec-evolution`](../../../.meshkore/modules/meshkore-spec/tasks/10-spec-evolution.md)
for the rationale.

---

## v25 — 2026-06-14 — Context: docs boundary, serialization hard-rule, wikilinks

Closes three gaps around `.meshkore/context/` surfaced when a cluster rebuilt
a parallel `docs/` tree that duplicated `context/` (the human standard page
never explained the boundary, so it was reinvented from scratch).

### What changed

- **`context/` vs `docs/` boundary is now explicit** (§3.5 "where things live"
  table + `context.relationship_to_docs`). `context/` = single source of truth
  for INVARIANTS (the WHAT, loaded into every spawn); `docs/<category>/` =
  categorized reference for the HOW (detail, on demand) that LINKS to context/
  and never copies an invariant. There is NO `docs/stack/` category — high-level
  stack is `context/stack.md`; deploy/ops detail is `docs/deploy/` + `docs/ops/`.
- **Serialization is now a `hard_rule`.** The daemon MUST serialize
  `.meshkore/context/` into every briefing (the `=== PROJECT CONTEXT === … ===
  END CONTEXT ===` block, before the role) per `context.serialization_to_agent`.
  Previously prose-only; a daemon injecting only the legacy single-file
  `docs/context.md` is now explicitly non-conformant.
- **No-duplication is now a `hard_rule`.** Never restate a context/ invariant in
  docs/. One fact, one home.
- **Wikilink cross-references standardized** (new `cross_references` key):
  `[[<path-relative-to-.meshkore>]]`, no extension, for see-also links between
  `.meshkore/` files. Daemon MAY lint broken links (non-blocking, advisory).
- `glossary.md` stays OPTIONAL + recommended (unchanged).

No frontmatter/schema break — purely additive rules + clarifications.

### How to apply (catch-up from v24)

1. Update `.meshkore/STANDARD_VERSION` from `24` to `25`.
2. Ensure your daemon serializes `context/` into briefings (the §3.5 block,
   before the role). If it still injects only the legacy `docs/context.md`,
   upgrade the daemon (the implementation that satisfies the new hard_rule).
3. If you built a parallel `docs/{product,stack,architecture}/` tree that
   restates `context/`, collapse it: keep invariants in `context/`, leave
   `docs/` as categorized reference that wikilinks to `context/`.
4. Adopt `[[wikilink]]` cross-references for see-also links between cluster
   files (optional but recommended).

---

## v24 — 2026-06-13 — Preamble propagation of cross-cutting rules

The §17 agent-CLI preamble (`agent-instructions.md`, rendered into
`CLAUDE.md` / `AGENTS.md` / `GEMINI.md`) now carries two load-bearing
rules it was missing, so a **standalone** agent session — one the
operator opens directly in the repo, with no daemon dispatching it —
boots with the same baseline knowledge the daemon already injects into
its own sub-agents.

### What changed

- **Preamble gains "Initiative-anchored execution" (§24).** A new
  numbered convention in the preamble: every turn anchors to an
  `(initiative, task)` pair; an unanchored turn must locate or create
  the matching initiative + task as its FIRST action. This was already
  a `hard_rule` since v23 and is embedded in the daemon's
  `_section_core_rules()`, but it never reached the static preamble —
  so a CLI session reading only `CLAUDE.md` didn't know about it.
- **Preamble "Where to dig deeper" now names `context/` and
  `protocols/`.** Explicit pointers to `.meshkore/context/` (§3.5 —
  standing invariant knowledge, including `criteria/`, the base
  acceptance criteria) and `.meshkore/protocols/` (§14 — the cluster's
  P-numbered runbooks), plus `.meshkore/docs/` and its `INDEX.md`.
- **`standard.json#agent_instructions.preamble_must_include`** gains
  two entries (items 7–8) so the requirement is machine-checkable.
- No file-format break. No new folder, no daemon endpoint, no schema
  change to any frontmatter.

### How to apply (catch-up from v23)

1. Update `.meshkore/STANDARD_VERSION` from `23` to `24`.
2. Re-sync the preamble: fetch
   `https://meshkore.com/standard/agent-instructions.md` and overwrite
   the block between `<!-- MESHKORE_PREAMBLE_BEGIN -->` and
   `<!-- MESHKORE_PREAMBLE_END -->` in
   `.meshkore/public/AGENT_INSTRUCTIONS.md`. Leave the
   `OPERATOR_CONTENT` block untouched.
3. Re-render the per-CLI targets from `AGENT_INSTRUCTIONS.md`: each of
   `CLAUDE.md` / `AGENTS.md` / `GEMINI.md` is the auto-render header
   comment (line 1, naming the CLI) + a blank line + the verbatim
   `AGENT_INSTRUCTIONS.md` body. (If your daemon build ships the §17
   render loop it does this on the version bump; if not, do it by hand
   — see the verification block in §17.6.)
4. No daemon restart required for the content itself. The
   initiative-anchoring BEHAVIOUR was already live as of py-1.12.23
   (v23); v24 only makes the static preamble state it.

### What this enables

A fresh Claude Code / Codex / Gemini CLI session opened directly in a
MeshKore repo — with no daemon driving it — now reads, from the file
its CLI auto-loads, that it must anchor to an initiative+task and
where the project's context, criteria, and runbooks live. Closes the
gap where that knowledge reached only daemon-dispatched sub-agents.

---

## v23 — 2026-06-10 — Initiative-anchored execution

Every agent turn anchors to `(initiative, task)`. The cockpit's
roadmap timeline becomes a live picture of "what is happening right
now in this project". Unanchored agent runs leave no trace in the
roadmap, can't be cross-referenced from the daily log, and break the
operator's mental model.

**Why now**: operator field report 2026-06-10 — "Lo ideal es
siempre estar ejecutando una tarea de una iniciativa. Cuando abrimos
un agente y le damos instrucciones, debería localizar la iniciativa
correspondiente si no se la damos, y dentro, coger la tarea de la
que estamos hablándole. Y si no hay iniciativa, crearla, con sus
tareas al planificar."

**Spec** (full details in `standard.md` §24):

- **Decision chain (3 cases)**:
  1. ANCHOR-PRESENT → acknowledge and continue.
  2. INITIATIVE-PRESENT, TASK-MISSING → pick or create a task.
  3. NEITHER-SET → match operator intent against existing
     initiatives; if no match, CREATE initiative + 1-3 tasks,
     anchor to the FIRST task.
- **Creation contract**: initiative file at
  `.meshkore/roadmap/initiatives/<slug>.md`, tasks at
  `.meshkore/modules/<module>/tasks/<id>-<slug>.md`. Slug shapes
  align with Standard §4 + §22.
- **Concurrency**: parallel agents create parallel initiatives;
  the cockpit roadmap timeline shows them all live.
- **Cockpit alignment**: the chat scope strip already shows the
  anchor. Agents that create/swap the anchor mid-conv push to the
  conv meta so the strip follows.

**Hard rule** added to `standard.json#hard_rules`:

> Every agent turn must anchor to (initiative_id, task_id) before
> performing code work. Agents whose dispatch arrived without an
> anchor MUST locate or create the matching initiative + task as
> their first action, then continue.

**Daemon**: py-1.12.23 embeds the rule in `_section_core_rules()`
(the universal-rules block already mounted in every agent's
briefing since py-1.7.0). New convention at
`.meshkore/docs/conventions/initiative-anchored-execution.md`.

**Apply manually to v22 clusters**:

1. Update `.meshkore/STANDARD_VERSION` from `22` to `23`.
2. Bump the local daemon to `py-1.12.23` (or later).
3. The cockpit needs no client-side change — the chat scope strip
   already renders the anchor; v23 changes what agents DO with it,
   not what the cockpit DISPLAYS.

No file-format break. Existing initiatives + tasks stay verbatim;
the rule is about how agents BEHAVE when they receive a turn.

---

## v22 — 2026-06-10 — Storage reporting endpoint (`GET /storage/usage`)

The daemon now exposes a per-bucket disk-usage breakdown of
`.meshkore/` so the cockpit can render a Storage panel and operators
can tune retention / trigger purges in informed ways.

**Why now**: with v19 file snapshots + v22 chat attachments now landing
on disk, `.meshkore/` grows in places the operator doesn't see at the
moment of writing. The old "find . -name X | du" loop was the only way
to spot which bucket was eating bytes. The cockpit needs a first-class
surface for capacity tuning.

**Spec** (full details in `standard.md` §23):

- `GET /storage/usage` — no auth, returns bytes + file counts per
  well-known bucket: `log`, `snapshots`, `uploads`, `queues`,
  `timeline`, `agents`, `modules`, `docs`, `roadmap`, `.runtime`,
  `credentials`.
- Each entry: `{name, bytes, files, exists, retention_days?}`. The
  retention field appears only when the daemon enforces a sweep for
  that bucket (today: `snapshots`=7, `uploads`=30).
- Top-level: `total_bytes`, `total_files`, `generated_at`,
  `cache_ttl_secs`.
- Server-side cache: 5 s TTL — polling at 1 Hz is safe; recomputing
  rglobs every tick is not.

**Daemon**: py-1.12.22 ships `StorageReport` (cached walker) + the
HTTP route. Plumbed into `Daemon.__init__`; the cockpit's Storage
panel hits the endpoint on Config zone visit.

**Apply manually to v21 clusters**:

1. Update `.meshkore/STANDARD_VERSION` from `21` to `22`.
2. Bump the local daemon to `py-1.12.22` (or later).
3. The cockpit's Storage panel will appear automatically — no
   per-cluster config change.

No file-format break. Frontmatter, snapshots, queues, uploads
unchanged; v22 is additive — the endpoint is new, nothing existing
moves.

---

## v21 — 2026-06-09 — Commit trailers: add `MeshKore:`, drop `Co-Authored-By:`

§9.1 revised. The mandatory trailer set on every LLM-authored commit
in a MeshKore repo is now exactly three:

```
Agent: <agent-role>
Model: <model-id>
MeshKore: py-<X.Y.Z>
```

`Co-Authored-By:` is REMOVED from the mandatory list — it carried no
information the other trailers + git's own author field don't already
record, and the operator's cross-repo convention is no-co-authoring.
MeshKore was the only repo using it; that's now corrected.

The new `MeshKore:` trailer quotes the daemon's `DAEMON_VERSION`
literal (`py-X.Y.Z`) at the moment the agent authored the commit.
The daemon embeds it in every subagent briefing, so the value is
always known. It lets `git log` filter the cohort of commits
produced under a given runtime — useful for tracing regressions to a
specific daemon release and for measuring how a new daemon feature
ripples through real work.

**Why now**: operator pointed out 2026-06-09 ("estás dejando una
información de co-authoring en cada commit"). The cross-repo
convention is no-co-authoring; MeshKore is the explicit exception
because agent-driven work is the norm, not the edge case. But the
exception should add semantic trailers, not the legacy display-name
boilerplate. While editing §9.1 we also added the daemon-version
trailer the operator asked for: "podrías complementar obviamente
esa info con la versión de MeshKore que estamos usando en ese momento".

**Spec** (full details in `standard.md` §9.1):

- Three required trailers, in order: `Agent:`, `Model:`, `MeshKore:`.
- Two optional trailers, only on standard bumps: `Standard: vN-1 → vN`
  + `Section: <top-level json key>`.
- `Co-Authored-By:` — DO NOT add. Removed from §9.1.

**Hard rule** updated in `standard.json#hard_rules`:

> Every commit produced by an LLM agent must carry `Agent:`, `Model:`,
> and `MeshKore:` git trailers identifying the agent role, the model
> id, and the cluster's daemon version. Do NOT add `Co-Authored-By:`
> — git author already records the committer.

**Daemon**: py-1.12.15 — the universal-rules block in
`_section_core_rules` now reproduces the new three-trailer template,
and the canonical preamble (`webapp/standard/agent-instructions.md`)
points at §9.1 with the updated example. Every subagent spawned from
py-1.12.15 onward stamps the new format without operator action.

**Apply manually to v20 clusters**:

1. Update `.meshkore/STANDARD_VERSION` from `20` to `21`.
2. Bump the local daemon to `py-1.12.15` (or later) — fetch the
   latest `daemon.py` from this repo or use the cockpit's
   daemon-update flow.
3. Going forward, new commits stop adding `Co-Authored-By:` and
   start adding `MeshKore: py-<your-daemon-version>`. Past commits
   are NOT rewritten (history is history); the new format applies
   forward only.
4. If you have a custom CLAUDE.md / AGENTS.md / .cursorrules with
   the §9.1 example hand-copied in, update the example to match
   §9.1 (or just delete the local copy and let the daemon's
   MESHKORE_PREAMBLE block be the source of truth).

No file-format break. Frontmatter, snapshots, chat-id refs (v20),
all untouched.

---

## v20 — 2026-06-09 — Chat references by `#<id>`

When an agent's chat output adds, removes, renames, defers, splits, or
otherwise touches an initiative or task, every mention of that item in
the same line MUST include `#<id>` (the item's frontmatter `id:` with
a `#` prefix). Bare titles ("the voting initiative") are no longer
acceptable for load-bearing mentions — the cockpit's chat renderer
turns `#<id>` into a click-target that scrolls the roadmap timeline to
the matching story.

**Why now**: the V108 cockpit timeline ships a `#<id>` chip at the head
of each initiative's title row. Without a matching chat convention,
agents would still narrate roadmap mutations in prose ("split the
voting initiative into two") and the operator would have to grep file
names to verify. With the convention in place, chat ↔ roadmap UI share
the same vocabulary and the operator pattern-matches by sight.

**Spec** (full details in `standard.md` §22):

- Id shape: `^[A-Za-z][A-Za-z0-9_-]{1,31}$`. Examples in production:
  `I18`, `I21`, `PAYMENTS`, `T-vote-API`, `WORK-21`.
- The `#` prefix is non-negotiable — the chat renderer regex looks for
  `#` + an id token.
- Exclusions: bare prose is fine when (a) planning aloud, (b)
  introducing a brand-new id in the same line, (c) writing narrative
  paragraphs where mentions aren't load-bearing.
- Canonical chat lines:
  - `✓ added #I18 task #T-vote-anon-toggle`
  - `✗ removed #T-fixture-loader from #I19 (folded into #T-spiral-prefetch)`
  - `↻ split #I21 into #I21 (chain) + #I27 (storage)`
  - `🔧 #I12 DEMO2 stub (AMOY_PRIVATE_KEY unset → testnet fixture)`
  - `✓ #I18 done (6/6 shipped). → #I19.`

**Hard rule** added to `standard.json#hard_rules`:

> Every chat mention of an initiative or task an agent is creating,
> modifying, deferring, splitting, or removing must include `#<id>`
> (the item's frontmatter id with a `#` prefix). Bare titles are not
> addressable in the cockpit UI.

**Daemon**: py-1.12.14 embeds the rule in the universal-rules block of
every agent's briefing (`daemon._section_core_rules`). Every spawn
from this version forward sees it — no client-side opt-in needed.

**Apply manually to v19 clusters**:

1. Update `.meshkore/STANDARD_VERSION` from `19` to `20`.
2. Bump the local daemon to `py-1.12.14` (or later) — fetch the latest
   `daemon.py` from this repo or run the cockpit's `/daemon/update`.
3. (Optional) Add `.meshkore/docs/conventions/chat-id-references.md`
   if you want a local copy — the rule lives in the daemon prompt
   regardless.
4. The cockpit V108 timeline already renders the `#<id>` chip; no
   client-side change needed.

No file-format break. Existing initiatives and tasks keep their ids
exactly as-is; the rule is about how agents REFER to them in chat.

**Convention**:
[`chat-id-references.md`](../../../.meshkore/docs/conventions/chat-id-references.md)

---

## v19 — 2026-06-09 — File snapshots (`.meshkore/snapshots/<YYYY-MM-DD>/<bucket>/`)

Pre-modification copies of files an agent is about to touch. Lets the
operator inspect any intermediate state between two commits without
needing 20 micro-commits. **Not** a git replacement — git owns merged
history; snapshots own the moments in between.

**Why now**: operators were either committing 20× per chat turn to keep
intermediate states reachable, or accepting that "anything between two
commits is lost". Both are wrong. A small, daemon-managed, retention-
bounded snapshot tree solves it.

**Spec** (full details in `standard.md` §20):

- Storage: `.meshkore/snapshots/<YYYY-MM-DD>/<bucket-id>/{_manifest.json, files/<src-path>}` — gitignored, lazy, retention-bounded.
- Bucket id: `<YYYYMMDD>-<HHMMSSmmm>-<idHint>-<descSlug>-<4hex>`; lexicographic = chronological.
- Manifest: `{ id, date, created_at, conv, agent_id, agent_type, initiative_id, task_id, msg_id, note, files: [{path, size, sha256, skipped?}], warnings }`.
- Limits: 50 files per bucket, 5 MB per file.

**HTTP routes** (daemon py-1.12.13+):

| Verb | Path |
|---|---|
| POST | `/snapshots` (create) |
| GET | `/snapshots?limit=N` (list newest-first) |
| GET | `/snapshots/<bucket>` (manifest) |
| GET | `/snapshots/<bucket>/files/<repo-relative-path>` (raw file body) |
| DELETE | `/snapshots/<bucket>` |

**WS events**: `snapshot.created`, `snapshot.removed`.

**Config** (`cluster.yaml`):

```yaml
snapshots:
  enabled: true            # default
  retention_days: 7        # default; range 1-365
```

Omit → defaults apply. Garbage collection runs opportunistically on every `POST /snapshots` (sweeps date dirs older than `retention_days`); no extra cron tick.

**Agent contract**: before any tool call that Writes/Edits an EXISTING file, agents MUST call `POST /snapshots` with the paths they're about to modify. Newly-created files are exempt. The canonical `AGENT_INSTRUCTIONS.md` preamble was updated to spell this out so every CLI agent CLAUDE.md / AGENTS.md / GEMINI.md inherits the rule via the daemon's auto-render.

**Daily-log integration**: every snapshot.create auto-appends a section to `.meshkore/log/<YYYY-MM-DD>.md` with bucket id + agent + file list + restore hint. So the diary tells the same story the chat does — interleaved with the file-change trail.

**Catch-up procedure for daemons running v18**:

1. Bump the local daemon binary to **py-1.12.13** or later. The new routes are no-op friendly until first use.
2. (Optional) Add to `cluster.yaml`:
   ```yaml
   snapshots:
     enabled: true
     retention_days: 7
   ```
   The defaults are identical, so most clusters can skip step 2.
3. Set `.meshkore/STANDARD_VERSION` to `19`.
4. Re-render the auto-managed AGENT_INSTRUCTIONS preamble (per v18 protocol) so the §17 preamble includes the new snapshot-contract paragraph. The render is idempotent — operator content under `OPERATOR_CONTENT_BEGIN` is preserved.
5. Restart the daemon process so the new HTTP routes are registered. `/reload` rebuilds state but does NOT reimport the script.

**Future cockpit UI** (out of scope for v19, planned for V108+): snapshot list inline on each chat message that triggered modifications, on each roadmap task card, and on each daily-log entry. Diff snapshot ↔ working tree from the cockpit. V19 ships storage + protocol; UI follows once the data path is settled.

**Anti-patterns**:
- Snapshotting newly-created files (no prior content to preserve).
- Treating snapshots as long-term archive — retention will drop old buckets.
- Restoring files behind the agent's back (operator instructs the agent to restore; agent reads the snapshot via `/snapshots/<bucket>/files/<path>` and writes the working tree).
- Committing the `.meshkore/snapshots/` directory — gitignored on purpose.

Specs:
- Standard §20 (`/standard#20-file-snapshots-v19`)
- HTTP surface in `standard.json#snapshots.http_routes`
- Daemon code: `Daemon.snapshot_*` family (helper, list, get, file, delete, gc) at `daemon.py` py-1.12.13+

---

## v18 — 2026-06-09 — Local agent CLI instructions (`.meshkore/public/AGENT_INSTRUCTIONS.md`)

Every cluster now ships a single canonical source of project-wide
instructions for any AI assistant the operator runs locally — Claude
Code, Codex, Gemini CLI, Cursor, Cline, Aider, Grok-Coder — and the
MeshKore daemon **renders** that source into the well-known per-CLI
files at the repository root (`CLAUDE.md`, `AGENTS.md`, `GEMINI.md`,
…) and keeps them in sync.

**Why now**: each agent CLI looks at a different file for project
rules and operators had been hand-maintaining the same content N
times — drift inevitable, MeshKore-specific conventions (network
resources from §16, commit attribution, standard evolution
protocol) often missing in one or another. Anchoring a single
source in the standard, with a daemon-managed preamble, fixes
compatibility once.

The new §17 in the standard renumbers the old "Project repo" to §18
and "Where everything else points" to §19 — no internal links were
referencing the old numbers, so the change is purely typographic in
the spec body. Cross-references in CHANGELOG / docs / daemon code
that point at §17 by literal string should be reviewed.

**Catch-up steps for daemons running v17**

1. In every cluster you manage, create
   `.meshkore/public/AGENT_INSTRUCTIONS.md` with the two markers
   defined in §17.2:
   - `<!-- MESHKORE_PREAMBLE_BEGIN -->` … `<!-- MESHKORE_PREAMBLE_END -->`
   - `<!-- OPERATOR_CONTENT_BEGIN -->` … `<!-- OPERATOR_CONTENT_END -->`
2. Fetch `https://meshkore.com/standard/agent-instructions.md` and
   paste its content into the preamble block. (Verbatim — no edits.
   The daemon will keep this fresh on subsequent bumps.)
3. If the operator already had a `CLAUDE.md` / `AGENTS.md` /
   `GEMINI.md` at the repo root with project-specific content, move
   the project-specific portions into the OPERATOR_CONTENT block.
4. Render to the three mandated targets at the repo root by writing
   each file as: `<auto-render header comment>` newline +
   verbatim contents of `AGENT_INSTRUCTIONS.md`. Mandated targets:
   `CLAUDE.md`, `AGENTS.md`, `GEMINI.md`.
5. Add a file-watch on `.meshkore/public/AGENT_INSTRUCTIONS.md` so
   operator edits re-render the three targets. Add the same render
   step to the existing standard-upgrade flow so a future preamble
   change propagates.
6. (Optional, mandated in v19) also render
   `.cursor/rules/meshkore.mdc` and `.clinerules`.

**Where it does NOT apply**

- Agents NOT running as a CLI in the operator's repo (the mesh's
  remote agents like meshkore-image-gen) are unaffected — they
  follow their own Tier-2 PublicCard contract from §16 / addressing.
- The OPERATOR_CONTENT block is operator-sovereign; the daemon never
  touches it. If you delete the markers, the daemon refuses to
  refresh the file rather than risk overwriting hand-edited content.

Specs:
- Standard §17 (`/standard#17-local-agent-cli-instructions-v18`)
- Canonical preamble: `/standard/agent-instructions.md`
- Operator playbook: `/reference/agents/local-instructions`

---

## v17 — 2026-06-09 — Network resources file (`.meshkore/public/RESOURCES.md`)

Every cluster now ships a verbatim local mirror of
`https://meshkore.com/standard/resources.md` at
`.meshkore/public/RESOURCES.md`. The file is a one-screen reference
for the network's primary entry points (Oracle, hub directory,
canonical agent URL pattern, deploy playbook, daemon upgrade flow)
so that any AI agent reading the project context immediately knows
what the mesh offers and where to look — without having to be told
in every prompt.

**Why now**: the mesh has worked for months but discoverability of
the mesh itself inside a project was poor. Operators were
re-explaining the same URLs in every conversation. A small file at
a predictable path closes the gap. Companion landing surface on the
public site at `meshkore.com/connect#call` shows the operator the
exact URL to paste.

**Catch-up steps for daemons running v16**

1. Copy `https://meshkore.com/standard/resources.md` to
   `.meshkore/public/RESOURCES.md` in every cluster you manage. The
   canonical block ends at `<!-- /canonical -->`; anything below is
   project-specific operator content and survives future refreshes.
2. When the standard-version-check task runs (daily; existing flow),
   add a step: if `STANDARD_VERSION` < `published_version`, re-fetch
   the canonical `resources.md` and overwrite the local copy above
   the marker. Same auth model and storage path; no new endpoints.
3. In every system-prompt template the daemon emits for subagents,
   add a one-line pointer like: *"MeshKore network resources at
   `.meshkore/public/RESOURCES.md` — Oracle, hub directory,
   agent-use patterns, daemon upgrade flow."* No need to embed the
   file content; the agent reads the path on demand.
4. (Optional, recommended) Mirror the same pointer in the project's
   root `CLAUDE.md` / `AGENTS.md` so agents that read the project
   docs first also find it.

**Where it does NOT apply**

- Skill invocations stay direct from caller to agent endpoint (no
  proxy via `meshkore.com`). The new file is metadata only.
- Live state (online/offline flags, message counters) is never
  baked into the file — query the hub for those.

Specs:
- Standard §16 (`/standard#16-network-resources-file-v17`)
- Canonical file: `/standard/resources.md`
- Web surface for "Use a specific agent": `/connect#call`

---

## v16 — 2026-06-08 — Chat-turn queue (`.meshkore/queues/<conv>.json`)

A per-conv FIFO of operator-authored prompts that wait their turn.
When a conv is mid-turn and the operator wants to line up a follow-up
without interrupting, they hit the clock-icon in the composer. The
queue persists to disk; the daemon auto-flushes the head when the
conv goes idle after the next turn final.

**Why now**: operators were either (a) sitting on the prompt for
minutes waiting for an agent to finish, (b) firing immediately and
interrupting the in-flight turn, or (c) losing context by switching
away. The queue lets them script a small sequence of follow-ups
without any of those failure modes. Field-reported 2026-06-08.

**Spec** (full details in `standard.md` §6.4):

- Storage: `.meshkore/queues/<conv>.json`, gitignored, created lazily.
- File shape: `{ conv, version, items: [{ id, text, created_at, position, status, sent_at?, failed_reason? }] }`.
- Status enum: `queued | sending | sent | failed | cancelled`.
- Limits: 50 items per queue, 64 KB per text.

**HTTP routes** (all auth-required, daemon py-1.12.12+):

| Verb | Path |
|---|---|
| GET | `/chat/conv/<conv>/queue` |
| POST | `/chat/conv/<conv>/queue` (enqueue) |
| POST | `/chat/conv/<conv>/queue/<id>/edit` |
| POST | `/chat/conv/<conv>/queue/<id>/move` |
| POST | `/chat/conv/<conv>/queue/<id>/promote` |
| DELETE | `/chat/conv/<conv>/queue/<id>` |

**WS events**: `queue.item.added | updated | removed | sent`.

**Auto-flush rule**: when the daemon emits `chat.assistant.final`
AND the conv's `live=false coordinating=false`, pop the head and
dispatch it through the normal `/chat/dispatch` path. One turn =
one flush; the head waits if it's not ready (e.g. status changed
to `sending` already by /promote).

**UI contract** (cockpit V107.41+):

- ChatComposer renders a queue button (clock icon) between play and
  paperclip. Shown when the conv is busy OR there are items waiting.
- Queued items render ABOVE the textarea, max-height 30vh, scrollable.
- Each row: drag handle (⋮⋮) + text (click to edit) + ✕ delete.
- Operator hits **play** to send NOW (current behavior, talks to the
  in-flight turn). Operator hits **clock** to enqueue.
- Live updates via WS — no polling.

**Future-proofing**: a planned multi-agent column view reads
`state.queues[conv]` per agent and renders each queue as a checklist
next to the agent's column. Same daemon contract — no new storage.

**Catch-up procedure** for v15 clusters:

1. Bump daemon to **py-1.12.12** or later.
2. Bump cockpit to **V107.41** or later.
3. Set `.meshkore/STANDARD_VERSION` to `16`.
4. Restart the daemon (`POST /reload` does NOT pick up new Python
   modules — needs a full kill + respawn).
5. The queue directory is created lazily; nothing to migrate.

**Scope exclusions** (not in v16):
- Optional auto-promote-all-on-idle modes (single-head-per-flush is the design).
- Cross-cluster queues (queue belongs to ONE daemon).
- Architect / sub-agent enqueue (they dispatch directly).

**Anti-patterns**:
- Auto-promoting all items in one round when the conv goes idle.
- Treating the queue as a substitute for tasks/initiatives.
- Writing directly to `convMap` for queued items — must flow through `/chat/dispatch`.

---

## v15 — 2026-06-08 — `draft` task status — non-dispatchable

New canonical task status: **`draft`** — "spec not finalised, not for
execution". An operator-authored placeholder that the architect MUST
NOT pick up in either Run-all or per-initiative dispatch.

**Rationale**: pre-v15 there was no way to distinguish "this task
exists but isn't ready to run" from "this task is queued for the
next pass" (both lived under `next` / `backlog` with no consistent
semantics). Operators on cavioca/ikamiro field-reported that the
architect kept trying to dispatch tasks they were still authoring,
forcing them to use `backlog` as a fake-draft state — which then
conflicted with the actual "parked idea" meaning of backlog.

**Dispatchability table** (v15):

| Status | Dispatchable? |
|---|---|
| `next`, `active`, `in_progress`, `doing`, `planned` | ✅ |
| `draft`, `done`, `cancelled`, `backlog` | ❌ |
| `blocked`, `pending-operator` | ❌ (awaiting external) |

**Operator-only transition**: only the operator promotes `draft → next`. The architect MUST NOT auto-promote (would defeat the purpose). The architect's end-of-pass `decisions:` bucket counts skipped drafts so the operator knows what was left out:

```
decisions: 4 task(s) status:draft skipped — operator owns promotion to next.
```

**Initiative completion**: an initiative with ANY child task in `draft` stays `active`. Same rule as `next` / `active` / `blocked`. The reconcile already filters naturally (`all_done = all(status == "done")`).

**Visual contract**: the cockpit renders `draft` with a dashed slate border on both the task id chip (TaskCard) and the StatusBadge in the initiative's task grid. Distinct from `next` (warm amber, ready) and `backlog` (gray, parked).

**Catch-up procedure** for v14 clusters:

1. Bump daemon to **py-1.12.11** or later — earlier versions don't know about `draft` and would dispatch them by mistake. (Cluster check: `curl https://daemon.meshkore.com:<port>/health | jq .version`.)
2. Bump cockpit to **V107.40** or later — earlier versions render unknown statuses as the generic gray fallback (technically safe but visually wrong).
3. Set `.meshkore/STANDARD_VERSION` to `15`.
4. Re-author any tasks the operator was using `backlog` as a fake-draft: change `status: backlog` → `status: draft` if the intent is "not ready, do not run".
5. Restart the daemon (`POST /reload` or kill+respawn) so spawn briefings include the v15 FORBIDDEN rule.

**Anti-patterns**:
- Architect promoting `draft → next` autonomously to "make Run-all work" — that's the bug the new state prevents.
- Operators using `draft` for "I'm thinking about this" and `backlog` for "I'm thinking about this later" — pick one (`draft` for not-ready, `backlog` for not-scheduled).
- Renaming all old `next` tasks to `draft` "just in case" — only mark `draft` when you genuinely don't want the architect to touch the spec.

---

## v14 — 2026-06-06 — Project context tree (`.meshkore/context/`)

A dedicated `.meshkore/context/` folder is now part of the standard.
It holds the project-wide INVARIANT knowledge that the daemon injects
into every agent spawn — theory-grounded in agent context engineering
(Anthropic, Cline memory-bank, Cursor `.cursorrules`, Claude Projects).

**Six canonical files + two growable folders**:

- `overview.md`, `product.md`, `stack.md`, `architecture.md`, `constraints.md` — required, ≤200-250 words each.
- `glossary.md` — optional, ≤250 words.
- `decisions/` — required folder, ADR-style entries each ≤100 words, named `YYYY-MM-DD-<slug>.md`.
- `criteria/` — optional folder, when-X-vs-Y rules each ≤100 words.

**Total budget**: 3000 words ≈ 4500 tokens. Daemon emits
`context_tree.token_estimate` in `/state`; cockpit shows the budget
badge; architect dispatch refuses to launch when over budget.

**Scope exclusions** — explicitly NOT in context:
- Audit → `.meshkore/protocols/`.
- Credentials → `.meshkore/credentials/`.
- Bookmarks, crons, links, logs, config — each in its own storage.
- Roadmap state → `.meshkore/roadmap/`.
- Module READMEs → `.meshkore/modules/<id>/README.md`.

**Why this exists**: pre-v14 the cockpit's Context tab fell back to
`docs/architecture/cluster-layout` (legacy doc) and the per-module
README we shipped in v13. Project-wide invariants had no home.
Operators (and agents) ended up re-debating settled decisions
because the context wasn't pinned in one place. Field-reported by
ikamiro 2026-06-06 — empty Context tab on a project that clearly
HAD context (idea, stack, vision) scattered across docs/.

**Catch-up procedure for v13 clusters**:

1. **Create** `.meshkore/context/` with the 6 canonical files.
   Bootstrap each ≤200-250 words using bullets/tables/code blocks.
2. **Seed** `decisions/README.md` and `decisions/<one initial ADR>.md`.
3. **Seed** `criteria/README.md` (optional but recommended).
4. **Migrate** any existing `.meshkore/docs/context.md` content
   into the new tree by intent (vision → overview, tech list →
   stack, decision rationale → decisions/, criteria → criteria/).
5. **Bump** `.meshkore/STANDARD_VERSION` to `14`.
6. **Bump** daemon to py-1.12.8+ (spawn-briefing integration).
7. **Reload** the daemon (`POST /reload` or restart).
8. Cockpit must be on V107.34+ to render the Context tab against the
   new tree.

**Word/token budget enforcement**: each file's frontmatter declares
`title` + `updated`. The daemon validates word counts at boot; over
the per-file limit logs a `warn` in `/health.warnings[]` and the
cockpit shows an over-budget badge. Hard refusal at the dispatch
boundary keeps agents from drowning in oversize context.

**Anti-patterns**:
- Essay paragraphs (context is bullets/tables/code).
- Marketing copy (no taglines).
- Duplicating module READMEs (different audience).
- Volatile state (task progress, last-deployed-sha — those live in roadmap).
- Frontmatter bloat (only title + updated allowed; no tags/owner/related).

---

## v13 — 2026-06-06 — `project` bucket required in cluster.yaml

The `project` meta module is no longer just a playbook convention —
it's a schema requirement. Every `cluster.yaml.modules` MUST now
include:

- One `project` entry: `kind: area`, no `parent:`, no `path:`. The
  meta bucket holding cross-cutting work.
- The 4 minimum area children of `project`: `deploy`, `docs`,
  `design`, `general` — each with `parent: project`.
- Real code modules as PEERS of `project` (not under it), each with
  `kind: code` and `path: <folder>/`.

**Why now.** Pre-v12.1 the convention lived only in the
Roadmap-Author playbook. Clusters created from older standards (or
hand-rolled `cluster.yaml`) ended up without the bucket, leaving
cross-cutting work mixed with code modules and the cockpit's Context
tab empty (no project-level docs). Ikamiro field-reported
2026-06-06 — `cluster.yaml` listed `general / web / api / ai / chain
/ ops` flat, and Context said "No project-level docs found yet."

**Catch-up procedure** for a pre-v13 cluster:

1. **Read your current `.meshkore/public/cluster.yaml`.** Identify which existing modules are cross-cutting areas (deploy, docs, design, ops, general, …) vs which own real source folders (web, api, daemon, …).
2. **Add the `project` bucket** as the first entry in `modules:`. `kind: area`, no parent, no path.
3. **Reparent every cross-cutting area** under `project` by adding `parent: project`. The 4 minimum (`deploy`, `docs`, `design`, `general`) must be present even if a module didn't exist before — add stub entries.
4. **Leave code modules at the top level**, with `kind: code` and `path:`. They stay as peers of `project`.
5. **Create `.meshkore/modules/<id>/README.md` stubs** for every module declared (including `project` itself + every child). Use the shape in [module-readmes playbook](https://meshkore.com/reference/prompts/roadmap-author/v1/module-readmes.md): ≤30 lines, status `stub`, frontmatter required.
6. **Bump `.meshkore/STANDARD_VERSION` to `13`**.
7. **Restart the daemon** so `/state` re-reads `cluster.yaml` and emits the new module tree.

**Anti-pattern**: keeping `general` as a top-level peer instead of a
child of `project`. The bucket only earns its keep when every
cross-cutting area sits under it.

**Backfill**: existing tasks keep their `category: <module-id>`
unchanged — the module id doesn't need to move, only its `parent:`
in `cluster.yaml`.

---

## v12 — 2026-06-02 — Commit attribution trailers (`Agent:` + `Model:`)

Every LLM-authored commit now MUST carry two new git trailers in
addition to the existing `Co-Authored-By:` line:

```
Agent: <agent-role>      # e.g. master, roadmap-architect, work-I13-CAT2
Model: <model-id>        # e.g. claude-opus-4-7, claude-sonnet-4-6
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
```

The `Agent:` value comes from the daemon's `agent_type` field or the
conv slug. The `Model:` value is the exact vendor model id. Human-
only commits MAY omit both. See `commit_attribution` in
`standard.json` and §9.1 of `standard.md` for the full spec.

**Why now.** Field-reported 2026-06-02: a 2-minute "resync" pass
from a roadmap-architect agent committed nothing but rewrote 21 files
to kick the daemon's stale in-memory state. From the git log alone
there's no way to tell whether a commit was a fast probe by a thin
agent or substantive work by a heavyweight one. The trailers make
role + model legible to `git log` / `blame` / analytics; survive
merges + cherry-picks unchanged; parseable via
`git interpret-trailers --parse`.

**Catch-up procedure** for a v11 cluster:

1. Append the v12 prose from §9.1 of `webapp/standard.md` into the
   local cluster's `closure-protocol.md` R2 (or wherever the local
   commit convention lives). Don't copy the rule body — link to
   <https://meshkore.com/standard#91-commit-attribution--agent--model-trailers-v12>.
2. If the cluster runs `meshkore-py`, bump to **py-1.12.7** or later
   — the daemon's spawn-time briefing includes the agent slug + model
   so subagents stamp commits automatically (no operator action).
3. Update the cluster's `CLAUDE.md` boot block: add a line under
   "Conventions" pointing at §9.1.
4. Set `.meshkore/STANDARD_VERSION` to `12`.

**Anti-pattern.** Embedding `Agent:` / `Model:` inside the commit
title or body prose ("// committed by roadmap-architect on Opus 4.7")
defeats the parser. Always use the trailer form at the end of the
body, after a blank line.

**Backfill.** Old commits stay unchanged. Don't rewrite history to
add trailers retroactively — the rule applies to commits authored
after the cluster bumps `.meshkore/STANDARD_VERSION` to 12.

---

## v11 — 2026-05-30 — Cycles (optional task timebox)

Linear-style **Cycles** land as an optional third dimension on the
roadmap. The two existing primitives — Initiative (work-stream) and
Task (one week of work) — are unchanged. Operators who don't want
sprint cadence ignore cycles entirely; nothing breaks.

New folder convention:

```
.meshkore/cycles/
  <YYYY>-w<ISO week>.md       # weekly cycles
  <YYYY>-<ID>.md              # ad-hoc cycles
```

Cycle file frontmatter:

```yaml
---
id: 2026-w22
title: "Sprint 22 — Cluster Cloud P0"
starts: 2026-05-25     # ISO date
ends:   2026-06-07
status: active         # planned | active | done
goal:   "Cerrar CC01..CC06 antes del cierre de junio."
---
```

Body is free-form markdown (planning notes, retrospective, links).

New optional task frontmatter key:

```yaml
cycle: 2026-w22
```

Missing key = task is not in any cycle (default). Present with an id
that doesn't match an existing `.meshkore/cycles/<id>.md` file =
the daemon emits a `task_cycle_broken` integrity hint on the next
briefing (warn, never block).

Daemon work (py-1.10.25 → next bump):
- `/state` payload gains a top-level `cycles:` slice:
  `[{ id, title, starts, ends, status, goal, tasks: [<id>...], path }]`.
  `tasks:` is the list of task ids whose frontmatter has
  `cycle: <this id>`.
- Each task in `/state.roadmap.tasks[]` gains the `cycle` field
  (string id or null).
- The FS-signature watcher now includes `.meshkore/cycles/` so
  adding / editing a cycle file fires `state.rebuilt` and the
  cockpit refreshes.
- StateIntegrityChecker gains `task_cycle_broken` (warn-not-block,
  symmetric with `task_initiative_broken`).

No new HTTP endpoints. Cycles are pure data — no scheduler, no
auto-rollover of unfinished work. The LLM rolls over on demand from
chat (same posture as `standard-versioning`).

Note on numbering: the original spec body (written 2026-05-26) said
"bump to v3", but the standard had already advanced to v10.1 by then.
This entry lands as v11 — the next additive bump after v10.1 — to
preserve monotonic numbering.

`STANDARD_VERSION` 10 → 11. No `DAEMON_VERSION` schema requirement
(daemon binary still serves the same endpoints; only payload shape
gains the additive `cycles:` slice).

---

## v10.1 — 2026-05-27 — Challenge-response auth (`/auth/challenge`)

Daemon adds `GET /auth/challenge?nonce=<n>` returning
`{ nonce, sig: HMAC-SHA256(portal-token, nonce), alg, version, ts }`.
Cockpit runs this handshake on every `switchToPort` to a new instance
when a token for that cluster is already in local storage, and refuses
to attach on mismatch. Defeats MITM-by-cert-leak: an attacker who
serves a valid TLS cert for `daemon.meshkore.com` (our wildcard is
public by design) still can't fake the daemon because they don't have
the operator's `portal-token`.

Backwards-compatible patch on top of v10. Daemons advertise the
capability via the `auth.challenge` feature flag; cockpits without
support fall back silently. **No `STANDARD_VERSION` bump** — additive
endpoint, no schema change. `DAEMON_VERSION` bumped `py-1.8.0 → py-1.8.1`.

Limitations:
- Doesn't protect first-time connection to a NEW cluster (no shared
  secret yet). Operator must verify `cluster_id` by hand on that
  initial bind.
- Future: per-cluster ACME (`<cluster_id>.daemon.meshkore.com` with
  its own cert) closes this fully. See initiative
  `local-tls-subdomain` → `daemon-multi-tenant-tls`.

## v10 — 2026-05-27 — Loopback TLS (daemon.meshkore.com)

The Python daemon now serves HTTPS + WSS on its port range
(5570-5589) when a bundled TLS cert is present alongside `daemon.py`.
The wildcard cert is for `*.daemon.meshkore.com`, which resolves to
`127.0.0.1` via a public Cloudflare DNS A record. This lets the
cockpit at `architect.meshkore.com` reach the local daemon from any
HTTPS origin without:

- mixed-content rejections (HTTPS page → `ws://localhost`)
- Chrome Local Network Access "Issues" (every public→loopback fetch
  was flagged; sessions accumulated thousands)
- the operator having to grant per-site permission via Chrome flags

The pattern is the same one Plex (`*.plex.direct`), Caddy local
HTTPS, and Tailscale Funnel use. Cert + key are deliberately public:
the only thing an attacker can do with them is impersonate
`daemon.meshkore.com` on their own loopback, which gives access to
nothing.

### What changed

- `daemon.py` (`DAEMON_VERSION = py-1.8.0`):
  - New `_find_tls_bundle()` + `_build_tls_context()` helpers.
  - `serve_forever()` wraps the listening socket with `ssl.SSLContext`
    when `tls/fullchain.pem` + `tls/privkey.pem` exist next to
    `daemon.py`. Falls back to plain HTTP otherwise (backwards
    compatible — operators who don't pull the `tls/` directory keep
    working unchanged).
  - `/health` exposes `tls: bool` and an `endpoint` field
    (`https://daemon.meshkore.com:<port>` when TLS is on,
    `http://localhost:<port>` otherwise).
  - `_features()` adds `tls.loopback` when TLS is enabled.
- `daemon/tls/` ships the wildcard cert + key + `README.md` +
  `renew.sh` (certbot DNS-01 against Cloudflare).
- Cockpit `MIN_DAEMON_VERSION = py-1.8.0`. The V47 upgrade modal
  fires on any daemon at `py-1.7.x` or below — daemons keep working
  on HTTP, but the operator is nudged to upgrade so the cockpit can
  flip TLS on by default.
- Cockpit feature flag `localStorage['mc-daemon-via-tls']` (or
  `?tls=1`) toggles the cockpit's URL scheme between
  `https://daemon.meshkore.com:<port>` and the legacy
  `http://localhost:<port>`. Surfaced in the Header About modal.

### How to apply (catch-up from v9)

1. `printf '10' > .meshkore/STANDARD_VERSION`
2. Run the daemon-upgrade flow:
   ```bash
   curl -fsS https://meshkore.com/reference/cluster/scripts/daemon.py \
     -o .meshkore/scripts/daemon.py
   curl -fsS https://meshkore.com/reference/cluster/scripts/tls/fullchain.pem \
     -o .meshkore/scripts/tls/fullchain.pem
   curl -fsS https://meshkore.com/reference/cluster/scripts/tls/privkey.pem \
     -o .meshkore/scripts/tls/privkey.pem
   chmod 600 .meshkore/scripts/tls/privkey.pem
   ```
3. Restart the daemon:
   ```bash
   curl -X POST http://localhost:$(cat .meshkore/.runtime/port)/shutdown \
     -H "Authorization: Bearer $(cat .meshkore/credentials/portal-token)"
   python3 .meshkore/scripts/daemon.py &
   ```
   Confirm the boot log says `tls=on (daemon.meshkore.com)`.
4. In the cockpit's Header → click the logo → About modal → toggle
   "TLS mode" to on. Reload. The cockpit will now hit
   `https://daemon.meshkore.com:<port>` for everything.

The catch-up is safe to skip — operators who don't apply it stay on
plain HTTP and continue to work, just with the mixed-content + LNA
Issues as before. The bump is mandatory only for operators who want
the silent / real-time cockpit experience over HTTPS.

### What this enables

- Real-time cockpit (`wss://` WebSocket events) when cockpit is
  served from any HTTPS origin (`architect.meshkore.com` today).
- Zero Chrome LNA Issues on session — no more 1k+ Issue
  accumulation while debugging.
- Foundation for the cockpit to talk to **multiple parallel
  daemons** without each one having to mint its own self-signed
  cert.

### See also

- [`local-tls-subdomain`](../../../.meshkore/roadmap/initiatives/local-tls-subdomain.md) initiative.
- [`P4-daemon-upgrade`](../../../.meshkore/protocols/P4-daemon-upgrade.md) protocol for daemon-only updates that don't touch the standard schema.

---

## v9 — 2026-05-26 — Project repo convention (multi-repo `.meshkore/` backup)

New §17 of the standard. Codifies what the operator should do with
their `.meshkore/` folder so it doesn't only live on the laptop:

- **Single-repo projects**: `.meshkore/` stays inside the source repo,
  gitignored selectively (same as today's behaviour, just documented).
- **Multi-repo projects**: `.meshkore/` becomes its **own private git
  repo** at `<org>/project` (or `<org>/<project-slug>`). The `.git/`
  lives **inside** the `.meshkore/` folder — the daemon stays unaware
  that the folder happens to be a git working copy.

A reference script `operator-sync.sh` plus a `launchctl` plist
template ships at
`meshkore.com/reference/cluster/scripts/operator-sync.sh` — default
cadence 30 min, single-instance-locked, drops a log under
`.meshkore/.runtime/logs/operator-sync.log`. Optional but recommended
until cluster cloud lands.

### Mandatory `.gitignore` patterns in any project repo

```
credentials/        # NEVER commit
.runtime/           # ephemeral daemon state
roadmap/state.json  # regenerable
roadmap/state.js    # regenerable
```

### Apply manually

For existing multi-repo projects:

```bash
cd <workspace>/.meshkore
git init -b main
# ... add gitignore from §17.3 ...
git add -A
git commit -m "Initial: import project workspace"
gh repo create <org>/project --private
git remote add origin git@github.com:<org>/project.git
git push -u origin main

# Install the auto-sync
curl -o scripts/operator-sync.sh \
  https://meshkore.com/reference/cluster/scripts/operator-sync.sh
chmod +x scripts/operator-sync.sh
# (install your platform's scheduler — see §17.4)
```

For single-repo projects: no action; existing gitignore rule from
v6 (`.meshkore/*` with `!.meshkore/public/` exceptions) is already
the right shape.

### Renaming from v8 pre-release

In an internal iteration the repo name `<org>/operator` was floated
before this changelog landed. The canonical name is `<org>/project`
from v9 onwards. If a cluster already created `<org>/operator`, just
`gh repo rename project --repo <org>/operator --yes` and update the
remote URL in `.meshkore/.git/config`.

### What did NOT change

- No daemon API change.
- No `.meshkore/` schema change — same folder shape.
- `cluster.yaml` schema unchanged.
- The L1/L2/L3 Quality Gates from v8 are unaffected.

---

## v8 — 2026-05-26 — Quality Gates (L1/L2/L3 mechanical safety net)

New §15 of the standard. Every code repo in a MeshKore cluster
opts in to three independent gate layers that catch mechanical
issues (types, lint, format, secrets, build, basic tests) before
they reach `main`.

### What changed

- New §15 — Quality Gates. Three layers documented:
  L1 (pre-commit, <10s, local), L2 (CI on push, GitHub Actions,
  1-5min), L3 (nightly drift, optional).
- New repo contract: every code repo MUST have `.github/workflows/ci.yml`
  + `.git/hooks/pre-commit` + `.meshkore.repo.yaml` with declared
  `stack` and `quality_gates_version`.
- Four stack templates shipped at
  `meshkore.com/reference/standards/quality-gates/`:
  `typescript/`, `cloudflare-worker/`, `rust/`, `python/`. Each has
  `ci.yml` + `pre-commit` + (where useful) nightly audit job.
- One-line installer:
  `curl … install.sh | bash -s <stack>`. Idempotent.
- Agent contract prompt at
  `meshkore.com/reference/standards/quality-gates/agent-prompt.md`.
  The daemon prepends this to every chat-dispatch in repos with
  Quality Gates installed. Forbids `--no-verify`, forbids
  `meshkore.skipL1`, forbids deleting gates to make red turn green.
- §16 takes the old §15 ("Where everything else points") slot —
  one-line rename, no semantic change.

### What did NOT change

- No daemon API change — the daemon doesn't need to know about
  Quality Gates yet. The hooks live on disk, the workflows live on
  GitHub. Integration with the prompt queue (L2 failure → agent's
  next dispatch) is deferred to the `quality-gates-standard`
  initiative, which depends on `context-versioning-and-agent-sync`.
- No `.meshkore/` schema change.
- No `cluster.yaml` schema change (`stack:` already declarable, just
  encouraged now).

### Apply manually (existing repos)

For each existing MeshKore-managed code repo:

```bash
cd <repo>
curl -sSf https://meshkore.com/reference/standards/quality-gates/install.sh \
  | bash -s <stack>     # typescript | rust | python | cloudflare-worker
git checkout -b chore/install-quality-gates
git add .github .meshkore.repo.yaml
git commit -m "chore: install meshkore quality gates v1 (<stack>)"
git push -u origin chore/install-quality-gates
# open PR, merge after L2 passes
```

Skip this for repos that are deliberately gate-less (scratchpads,
archived). Architect's repo panel will badge them yellow.

### Note on v7

v7 was an in-flight bump that didn't reach the public version file
before v8 work landed. The standard sequence ships v6 → v8 to keep
the public version monotonic. Nothing functional was added in v7.

---

## v6 — 2026-05-20 — Adoption polish (timeline vocab + migrate wizard + verify + token clarity)

This is a polish bump driven by operator feedback after a real-world
migration of an external repo (charms-wallet) hit eight distinct
friction points. No new files in `.meshkore/`; the changes are
documentation completeness + script hygiene + redirect cleanup.

### What changed

- **New §6.3 in the standard** — full timeline event vocabulary
  (`task.*`, `chat.*`, `tool.*`, `commit.*`, `deploy.*`, `cron.*`,
  `state.rebuilt`, `links.updated`, `protocols.updated`,
  `bookmarks.updated`, `daemon.shutdown`). §6.2 previously said "see
  spec v1 §11" but §11 is versioning — now fixed.
- **§3 clarification** — `.meshkore/STANDARD_VERSION` is the
  canonical answer to "which standard does this repo apply"; new
  table documents the spec ↔ manifest ↔ daemon relationship (three
  independent versions that may drift).
- **New wizard prompt** at
  `/reference/prompts/migrate-existing-repo.md` — the missing
  counterpart to `project-from-scratch`, covering ad-hoc legacy layouts
  (`_rjj/`, `_docs/`, `notes/`, …) with deterministic legacy → canonical
  mappings.
- **New `verify.py`** at `/reference/cluster/scripts/verify.py` — runs
  the §2/§3/§4/§5 conformance checks (layout, cluster.yaml fields,
  frontmatter, gitignore contract, STANDARD_VERSION freshness vs the
  published version, links.yaml shape, protocol filenames). Stdlib-only.
- **`migrate-tasklist.py` fixed** — was emitting to the pre-v3 layout
  (`roadmap/tasks/<module>/`); now emits to the §2 canonical
  (`modules/<module>/tasks/`).
- **`/changelog` aliases land** — `/changelog`, `/changelog.md`,
  `/standard/changelog`, `/standard/changelog.md` all 301 to
  `/standard/CHANGELOG.md`. Previously every variant 404'd.
- **Token policy clarified in `cluster/operate.md`** — explicit list
  of read endpoints (no auth) vs write endpoints (bearer required);
  the architect's localhost-only trust boundary is documented.
- **Editor boot blocks self-identify** — CLAUDE.md, .cursorrules,
  .windsurfrules, copilot-instructions.md each open with a comment
  naming the source URL + "kept local, gitignored" so an operator
  arriving at a repo recognises them instantly instead of asking "is
  this ours or someone else's?".
- **`/reference/manifest.json` cross-refs** — gains `version`,
  `changelog`, and `version_relationship` fields under
  `canonical_standard`, pointing at the integer endpoint + per-version
  blocks. Bumped to manifest schema v5.

### How to apply (catch-up from v5)

Backward compatible. Nothing in this bump forces a folder reshape.

1. **Pull the latest daemon**: `webapp/reference/cluster/scripts/daemon.py`.
   No new endpoints; only consistency.

2. **Pull the new `verify.py`** and run it once:
   ```bash
   curl -fsSL https://meshkore.com/reference/cluster/scripts/verify.py \
     -o .meshkore/scripts/verify.py
   python3 .meshkore/scripts/verify.py
   ```
   Address every ERROR; treat WARN as discretionary.

3. **Update your editor boot block** if you keep a local copy at repo
   root — the new template starts with a self-identifying comment.
   Drop your old `CLAUDE.md` / `.cursorrules` / `.windsurfrules` and
   re-curl from `/reference/cluster/editor-rules/`.

4. **Bump the marker**: `echo 6 > .meshkore/STANDARD_VERSION`.

### What this enables

- A second wizard (`migrate-existing-repo`) means the *"apply the
  standard"* prompt now has the right entry point for both empty
  repos and pre-existing ones.
- Agents finally have a programmatic conformance check (`verify.py`).
- Timeline event vocabulary is exhaustive enough that the architect's
  Diary panel (V43) can render every event type without surprises.
- No more *"which version am I on?"* ambiguity — the integer in
  `.meshkore/STANDARD_VERSION` is the single source of truth.

### Operator feedback this addressed

The full migration feedback from 2026-05-15 is preserved in
`.meshkore/log/2026-05-20.md`. Friction items #1–#8 are now closed
(token contradiction, missing changelog URL, stale migrate script,
missing event vocab, no self-identifying headers, no verify script,
no existing-repo wizard, no spec/manifest/daemon relationship doc).
The two open items — "bootstrap script for any legacy layout" and
"daemon-side `applied_standard` check at boot" — are covered
respectively by the new `migrate-existing-repo` prompt + `verify.py`
combo and by `verify.py`'s network check against `/standard/version`.

---

## v5 — 2026-05-20 — Protocols (reusable multi-scope runbooks)

### What changed

- New top-level folder **`.meshkore/protocols/`** holds named,
  indexed, reusable runbooks for multi-step work that crosses scopes
  (docs + code + commit + deploy).
- Identifier scheme **`P<N>`** added to the cluster's identifier
  family (P joins T, V, D, C, N, AG). One global numbering.
- New standard section **§14** describes the file shape, run-log
  shape, daemon surface, and "apply P<N>" semantics.
- Python daemon gains four endpoints:
  - `GET  /protocols`            no-auth, full list
  - `GET  /protocols/<id>`       no-auth, raw markdown + frontmatter
  - `GET  /protocols/<id>/runs`  no-auth, last 50 run entries
  - WS broadcast: `protocols.updated` on file change
- Gitignore exception added: protocols + INDEX are committed; run
  logs stay per-machine.
- Two protocols ship with the standard so projects have working
  references:
  - **P1** — bump-standard-version (the very protocol that produced
    this section).
  - **P2** — deploy-project (build + lint + clean-workspace check +
    per-module deploy via `links.yaml` + post-deploy patch + verify).
- Editor boot blocks (CLAUDE.md / .cursorrules / .windsurfrules /
  copilot-instructions.md) teach agents the "apply P<N>" pattern.

### How to apply (catch-up from v4)

**Backward compatible** — projects without `.meshkore/protocols/`
work exactly as before; the daemon treats a missing folder as an
empty protocol set.

1. **Create the folder** + index:
   ```bash
   mkdir -p .meshkore/protocols/log
   curl -fsSL https://meshkore.com/reference/cluster/templates/protocol.md \
     -o .meshkore/protocols/_template.md     # reference template (delete later)
   ```

2. **Update `.gitignore`** so protocols are committed but run logs
   stay local:
   ```
   !.meshkore/protocols/
   .meshkore/protocols/log/
   ```

3. **Write your first protocol** if useful (deploy, release, audit,
   data migration — whatever you re-explain to your agents most
   often). Follow the schema in §14.3.

4. **Bump the marker**: `echo 5 > .meshkore/STANDARD_VERSION`.

5. **Pull the latest daemon** so `/protocols` endpoints work. Older
   daemons simply don't expose them — no breakage.

### What this enables

- "Apply protocol P<N>" replaces "let me explain again how we ship
  a standard version / deploy the project / run a release".
- Cron-driven scheduled work can reference a protocol id instead of
  inlining a long command.
- The architect's cockpit (next iteration) renders the protocols
  list, lets the operator launch a run, and streams step-by-step
  outcome from the WS.
- Audit: every protocol run leaves a markdown log entry with commit
  sha + deploy IDs + per-step verdict. No more "what did we change in
  the last release" archaeology.

---

## v4 — 2026-05-20 — Links registry (`.meshkore/public/links.yaml`)

### What changed

- New committed file **`.meshkore/public/links.yaml`** declares, per
  module: local URL + start command, production URL + provider +
  deployed sha/version/timestamp, current repo branch + head sha.
- New standard section **§13** describes the schema, the
  agent-maintenance contract, and the daemon surface.
- Python daemon gains four endpoints:
  - `GET  /links`         no-auth, full registry as JSON
  - `GET  /links/<id>`    no-auth, single module
  - `POST /links/<id>`    bearer-auth, patches the file atomically
  - WS broadcast: `links.updated` on every rewrite
- Commit convention §6 extended: any commit that changes where a module
  runs, where it deploys, what branch it lives on, or what version it
  carries MUST update `links.yaml` in the same commit.
- Editor boot blocks (CLAUDE.md / .cursorrules / .windsurfrules /
  copilot-instructions.md) extended to remind agents to keep
  `links.yaml` in sync on every meaningful module change.

### How to apply (catch-up from v3)

**Backward compatible** — projects that don't deploy anything may
skip the file entirely; the daemon treats a missing file as
`{ version: 1, modules: [] }`.

1. **Create `.meshkore/public/links.yaml`** with at minimum:

   ```yaml
   version: 1
   modules: []
   ```

2. **For every module that has a reachable surface** (local port,
   production URL, or both), add an entry. Use the template at
   <https://meshkore.com/reference/cluster/templates/links.yaml> or
   read §13.1 of the standard.

3. **Bump the marker**: `echo 4 > .meshkore/STANDARD_VERSION`.

4. **Pull the latest daemon** at
   `webapp/reference/cluster/scripts/daemon.py` so the `/links`
   endpoints work. Older daemons will simply ignore the file — no
   breakage.

5. **Going forward**: every time you deploy a module, switch its
   active branch, or bump its version, update its entry in
   `links.yaml` in the same commit. The architect's cockpit will
   surface the registry in a future release (the file already drives
   the daemon today).

### What this enables

- Single source of truth for "where is everything deployed and at what
  version" — no more ad-hoc `links.md` lists or scattered per-module
  README sections.
- Cron-driven deploy agents can patch `prod.deployed_*` fields via
  `POST /links/<id>` immediately after a successful deploy.
- The cockpit gains a Deployments view (future task) that reads
  `/links` over the WS event stream.

---

## v3 — 2026-05-19 — Daemon-owned cron scheduler

### What changed

- `cluster.yaml` gains two **optional** top-level keys:
  - `crons:` — list of cron jobs. Each entry: `id`, `name`,
    `schedule` (5-field POSIX cron expression), `cmd`, optional
    `cwd`, `env`, `enabled`, `max_runtime_sec`, `restart_policy`,
    `retention_runs`, `destructive`.
  - `crons_owner:` — the `device_id` of the **coordinator** daemon.
    Only this daemon fires jobs; peers tick at 10 s intervals and
    emit `cron.would_have_fired` events. (Coordinator failover is
    deferred to Cluster Cloud P1.)
- `.meshkore/.runtime/crons.json` is the per-machine runtime state
  (last_run, next_run, history). Gitignored, recreated on first
  scheduler tick.
- `.meshkore/.runtime/logs/cron/<job_id>/<ts>.log` holds the captured
  stdout/stderr of each run.
- The Python daemon (`webapp/reference/cluster/scripts/daemon.py`)
  validates the `crons:` block on load. Unknown / malformed entries
  are skipped with a logged warning; the rest of the daemon's
  features (state, agents, WS) continue normally.
- Schema reference:
  [`docs/conventions/cluster-yaml-crons.md`](../../../.meshkore/docs/conventions/cluster-yaml-crons.md).
- Architecture diagram:
  [`docs/architecture/daemon.md § Cron scheduler`](../../../.meshkore/docs/architecture/daemon.md).

### How to apply (catch-up from v2)

**Backward compatible** — a v2 daemon ignores the new keys; a v3
daemon validates and processes them. No `.meshkore/` reshape needed.
The catch-up is purely additive:

1. **Pull the latest daemon** at `webapp/reference/cluster/scripts/daemon.py`
   (or re-clone the repo) so your local daemon has the v3 validator.
   The TypedDicts and `_validate_crons_block` were added in commit
   `eb1ce40` on branch `daemon/cron-scheduler` and merged to `main`
   when the cron scheduler is feature-complete.
2. **Optional**: add a `crons:` block + `crons_owner:` field to your
   `cluster.yaml`. If you don't want crons yet, do nothing — the
   daemon parses an empty `crons:` set without warning.
3. **Bump the marker**: `echo 3 > .meshkore/STANDARD_VERSION`.
4. **Restart the daemon** so it re-reads `cluster.yaml`. The new
   block is visible at `GET /cron/list` (when D-CRON-04 ships).

If you have an older daemon (no v3 validator) and the upstream
operator has already added a `crons:` block: nothing breaks — your
daemon's `parse_simple_yaml` reads the keys but `Cluster` doesn't
expose them. Upgrade the daemon when you want the feature.

### What this enables

- Replacement of macOS LaunchAgent / cron-tab / GH-Actions cron with
  one portable, stdlib-only Python scheduler that works on every OS
  Python runs on.
- Single-coordinator firing — no double-run when the same repo lives
  on multiple machines.
- Cockpit observability (CronsPanel + log viewer + manual trigger
  arriving with the cron-dashboard initiative).

---

## v2 — pre-changelog anchor

The `STANDARD_VERSION` marker was bumped from 1 to 2 between
2026-05-12 (the spec-evolution task's "today is 1" anchor) and the
creation of this file. Specific changes weren't tracked in changelog
form because this file didn't exist yet. From v3 forward, every bump
carries a section here.

---

## v1 — initial anchor (2026-05-12)

The first formally-versioned shape of `.meshkore/`:

- `.meshkore/public/cluster.yaml` (committed) — cluster identity,
  bootstrap URLs, admission policy, module list.
- `.meshkore/credentials/` (gitignored) — per-machine secrets.
- `.meshkore/agents/` (gitignored) — declared agent identities.
- `.meshkore/modules/<id>/tasks/*.md` — work units, scoped per
  module.
- `.meshkore/docs/{architecture,conventions,deploy,ops,security}/`
  — internal documentation.
- `.meshkore/roadmap/{initiatives,log,manual-tasks.md}` — planning.
- `.meshkore/log/<YYYY-MM-DD>.md` — daily activity log (operator
  convention added 2026-05-12).
- Standard discovery endpoint at `meshkore.com/standard/version`
  (this file's sibling).
