385 lines
15 KiB
Markdown
385 lines
15 KiB
Markdown
|
|
# Human Mirror Rules (Layer 3 → derived markdown views)
|
||
|
|
|
||
|
|
## Why this document exists
|
||
|
|
|
||
|
|
The engineering layer V1 stores facts as typed entities and
|
||
|
|
relationships in a SQL database. That representation is excellent
|
||
|
|
for queries, conflict detection, and automated reasoning, but
|
||
|
|
it's terrible for the human reading experience. People want to
|
||
|
|
read prose, not crawl JSON.
|
||
|
|
|
||
|
|
The Human Mirror is the layer that turns the typed entity store
|
||
|
|
into human-readable markdown pages. It's strictly a derived view —
|
||
|
|
nothing in the Human Mirror is canonical, every page is regenerated
|
||
|
|
from current entity state on demand.
|
||
|
|
|
||
|
|
This document defines:
|
||
|
|
|
||
|
|
- what the Human Mirror generates
|
||
|
|
- when it regenerates
|
||
|
|
- how the human edits things they see in the Mirror
|
||
|
|
- how the canonical-vs-derived rule is enforced (so editing the
|
||
|
|
derived markdown can't silently corrupt the entity store)
|
||
|
|
|
||
|
|
## The non-negotiable rule
|
||
|
|
|
||
|
|
> **The Human Mirror is read-only from the human's perspective.**
|
||
|
|
>
|
||
|
|
> If the human wants to change a fact they see in the Mirror, they
|
||
|
|
> change it in the canonical home (per `representation-authority.md`),
|
||
|
|
> NOT in the Mirror page. The next regeneration picks up the change.
|
||
|
|
|
||
|
|
This rule is what makes the whole derived-view approach safe. If
|
||
|
|
the human is allowed to edit Mirror pages directly, the
|
||
|
|
canonical-vs-derived split breaks and the Mirror becomes a second
|
||
|
|
source of truth that disagrees with the entity store.
|
||
|
|
|
||
|
|
The technical enforcement is that every Mirror page carries a
|
||
|
|
header banner that says "this file is generated from AtoCore
|
||
|
|
entity state, do not edit", and the file is regenerated from the
|
||
|
|
entity store on every change to its underlying entities. Manual
|
||
|
|
edits will be silently overwritten on the next regeneration.
|
||
|
|
|
||
|
|
## What the Mirror generates in V1
|
||
|
|
|
||
|
|
Three template families, each producing one or more pages per
|
||
|
|
project:
|
||
|
|
|
||
|
|
### 1. Project Overview
|
||
|
|
|
||
|
|
One page per registered project. Renders:
|
||
|
|
|
||
|
|
- Project header (id, aliases, description)
|
||
|
|
- Subsystem tree (from Q-001 / Q-004 in the query catalog)
|
||
|
|
- Active Decisions affecting this project (Q-008, ordered by date)
|
||
|
|
- Open Requirements with coverage status (Q-005, Q-006)
|
||
|
|
- Open ValidationClaims with support status (Q-010, Q-011)
|
||
|
|
- Currently flagged conflicts (from the conflict model)
|
||
|
|
- Recent changes (Q-013) — last 14 days
|
||
|
|
|
||
|
|
This is the most important Mirror page. It's the page someone
|
||
|
|
opens when they want to know "what's the state of this project
|
||
|
|
right now". It deliberately mirrors what `current-state.md` does
|
||
|
|
for AtoCore itself but generated entirely from typed state.
|
||
|
|
|
||
|
|
### 2. Decision Log
|
||
|
|
|
||
|
|
One page per project. Renders:
|
||
|
|
|
||
|
|
- All active Decisions in chronological order (newest first)
|
||
|
|
- Each Decision shows: id, what was decided, when, the affected
|
||
|
|
Subsystem/Component, the supporting evidence (Q-014, Q-017)
|
||
|
|
- Superseded Decisions appear as collapsed "history" entries
|
||
|
|
with a forward link to whatever superseded them
|
||
|
|
- Conflicting Decisions get a "⚠ disputed" marker
|
||
|
|
|
||
|
|
This is the human-readable form of the engineering query catalog's
|
||
|
|
Q-014 query.
|
||
|
|
|
||
|
|
### 3. Subsystem Detail
|
||
|
|
|
||
|
|
One page per Subsystem (so a few per project). Renders:
|
||
|
|
|
||
|
|
- Subsystem header
|
||
|
|
- Components contained in this subsystem (Q-001)
|
||
|
|
- Interfaces this subsystem has (Q-003)
|
||
|
|
- Constraints applying to it (Q-007)
|
||
|
|
- Decisions affecting it (Q-008)
|
||
|
|
- Validation status: which Requirements are satisfied,
|
||
|
|
which are open (Q-005, Q-006)
|
||
|
|
- Change history within this subsystem (Q-013 scoped)
|
||
|
|
|
||
|
|
Subsystem detail pages are what someone reads when they're
|
||
|
|
working on a specific part of the system and want everything
|
||
|
|
relevant in one place.
|
||
|
|
|
||
|
|
## What the Mirror does NOT generate in V1
|
||
|
|
|
||
|
|
Intentionally excluded so the V1 implementation stays scoped:
|
||
|
|
|
||
|
|
- **Per-component detail pages.** Components are listed in
|
||
|
|
Subsystem pages but don't get their own pages. Reduces page
|
||
|
|
count from hundreds to dozens.
|
||
|
|
- **Per-Decision detail pages.** Decisions appear inline in
|
||
|
|
Project Overview and Decision Log; their full text plus
|
||
|
|
evidence chain is shown there, not on a separate page.
|
||
|
|
- **Cross-project rollup pages.** No "all projects at a glance"
|
||
|
|
page in V1. Each project is its own report.
|
||
|
|
- **Time-series / historical pages.** The Mirror is always
|
||
|
|
"current state". History is accessible via Decision Log and
|
||
|
|
superseded chains, but no "what was true on date X" page exists
|
||
|
|
in V1 (Q-015 is v1-stretch in the query catalog for the same
|
||
|
|
reason).
|
||
|
|
- **Diff pages between two timestamps.** Same reasoning.
|
||
|
|
- **Render of the conflict queue itself.** Conflicts appear
|
||
|
|
inline in the relevant Mirror pages with the "⚠ disputed"
|
||
|
|
marker and a link to `/conflicts/{id}`, but there's no
|
||
|
|
Mirror page that lists all conflicts. Use `GET /conflicts`.
|
||
|
|
- **Per-memory pages.** Memories are not engineering entities;
|
||
|
|
they appear in context packs and the review queue, not in the
|
||
|
|
Human Mirror.
|
||
|
|
|
||
|
|
## Where Mirror pages live
|
||
|
|
|
||
|
|
Two options were considered. The chosen V1 path is option B:
|
||
|
|
|
||
|
|
**Option A — write Mirror pages back into the source vault.**
|
||
|
|
Generate `/srv/storage/atocore/sources/vault/mirror/p05/overview.md`
|
||
|
|
so the human reads them in their normal Obsidian / markdown
|
||
|
|
viewer. **Rejected** because writing into the source vault
|
||
|
|
violates the "sources are read-only" rule from
|
||
|
|
`tool-handoff-boundaries.md` and the operating model.
|
||
|
|
|
||
|
|
**Option B (chosen) — write Mirror pages into a dedicated AtoCore
|
||
|
|
output dir, served via the API.** Generate under
|
||
|
|
`/srv/storage/atocore/data/mirror/p05/overview.md`. The human
|
||
|
|
reads them via:
|
||
|
|
|
||
|
|
- the API endpoints `GET /mirror/{project}/overview`,
|
||
|
|
`GET /mirror/{project}/decisions`,
|
||
|
|
`GET /mirror/{project}/subsystems/{subsystem}` (all return
|
||
|
|
rendered markdown as text/markdown)
|
||
|
|
- a future "Mirror viewer" in the Claude Code slash command
|
||
|
|
`/atocore-mirror <project>` that fetches the rendered markdown
|
||
|
|
and displays it inline
|
||
|
|
- direct file access on Dalidou for power users:
|
||
|
|
`cat /srv/storage/atocore/data/mirror/p05/overview.md`
|
||
|
|
|
||
|
|
The dedicated dir keeps the Mirror clearly separated from the
|
||
|
|
canonical sources and makes regeneration safe (it's just a
|
||
|
|
directory wipe + write).
|
||
|
|
|
||
|
|
## When the Mirror regenerates
|
||
|
|
|
||
|
|
Three triggers, in order from cheapest to most expensive:
|
||
|
|
|
||
|
|
### 1. On explicit human request
|
||
|
|
|
||
|
|
```
|
||
|
|
POST /mirror/{project}/regenerate
|
||
|
|
```
|
||
|
|
|
||
|
|
Returns the timestamp of the regeneration and the list of files
|
||
|
|
written. This is the path the human takes when they've just
|
||
|
|
curated something into project_state and want to see the Mirror
|
||
|
|
reflect it immediately.
|
||
|
|
|
||
|
|
### 2. On entity write (debounced, async, per project)
|
||
|
|
|
||
|
|
When any entity in a project changes status (candidate → active,
|
||
|
|
active → superseded), a regeneration of that project's Mirror is
|
||
|
|
queued. The queue is debounced — multiple writes within a 30-second
|
||
|
|
window only trigger one regeneration. This keeps the Mirror
|
||
|
|
"close to current" without generating a Mirror update on every
|
||
|
|
single API call.
|
||
|
|
|
||
|
|
The implementation is a simple dict of "next regeneration time"
|
||
|
|
per project, checked by a background task. No cron, no message
|
||
|
|
queue, no Celery. Just a `dict[str, datetime]` and a thread.
|
||
|
|
|
||
|
|
### 3. On scheduled refresh (daily)
|
||
|
|
|
||
|
|
Once per day at a quiet hour, every project's Mirror regenerates
|
||
|
|
unconditionally. This catches any state drift from manual
|
||
|
|
project_state edits that bypassed the entity write hooks, and
|
||
|
|
provides a baseline guarantee that the Mirror is at most 24
|
||
|
|
hours stale.
|
||
|
|
|
||
|
|
The schedule runs from the same machinery as the future backup
|
||
|
|
retention job, so we get one cron-equivalent system to maintain
|
||
|
|
instead of two.
|
||
|
|
|
||
|
|
## What if regeneration fails
|
||
|
|
|
||
|
|
The Mirror has to be resilient. If regeneration fails for a
|
||
|
|
project (e.g. a query catalog query crashes, a template rendering
|
||
|
|
error), the existing Mirror files are **not** deleted. The
|
||
|
|
existing files stay in place (showing the last successful state)
|
||
|
|
and a regeneration error is recorded in:
|
||
|
|
|
||
|
|
- the API response if the trigger was explicit
|
||
|
|
- a log entry at warning level for the async path
|
||
|
|
- a `mirror_regeneration_failures` table for the daily refresh
|
||
|
|
|
||
|
|
This means the human can always read the Mirror, even if the
|
||
|
|
last 5 minutes of changes haven't made it in yet. Stale is
|
||
|
|
better than blank.
|
||
|
|
|
||
|
|
## How the human curates "around" the Mirror
|
||
|
|
|
||
|
|
The Mirror reflects the current entity state. If the human
|
||
|
|
doesn't like what they see, the right edits go into one of:
|
||
|
|
|
||
|
|
| What you want to change | Where you change it |
|
||
|
|
|---|---|
|
||
|
|
| A Decision's text | `PUT /entities/Decision/{id}` (or `PUT /memory/{id}` if it's still memory-layer) |
|
||
|
|
| A Decision's status (active → superseded) | `POST /entities/Decision/{id}/supersede` (V1 entity API) |
|
||
|
|
| Whether a Component "satisfies" a Requirement | edit the relationship directly via the entity API (V1) |
|
||
|
|
| The current trusted next focus shown on the Project Overview | `POST /project/state` with `category=status, key=next_focus` |
|
||
|
|
| A typo in a generated heading or label | edit the **template**, not the rendered file. Templates live in `templates/mirror/` (V1 implementation) |
|
||
|
|
| Source of a fact ("this came from KB-CAD on day X") | not editable by hand — it's automatically populated from provenance |
|
||
|
|
|
||
|
|
The rule is consistent: edit the canonical home, regenerate (or
|
||
|
|
let the auto-trigger fire), see the change reflected in the
|
||
|
|
Mirror.
|
||
|
|
|
||
|
|
## Templates
|
||
|
|
|
||
|
|
The Mirror uses Jinja2-style templates checked into the repo
|
||
|
|
under `templates/mirror/`. Each template is a markdown file with
|
||
|
|
placeholders that the renderer fills from query catalog results.
|
||
|
|
|
||
|
|
Template list for V1:
|
||
|
|
|
||
|
|
- `templates/mirror/project-overview.md.j2`
|
||
|
|
- `templates/mirror/decision-log.md.j2`
|
||
|
|
- `templates/mirror/subsystem-detail.md.j2`
|
||
|
|
|
||
|
|
Editing a template is a code change, reviewed via normal git PRs.
|
||
|
|
The templates are deliberately small and readable so the human
|
||
|
|
can tweak the output format without touching renderer code.
|
||
|
|
|
||
|
|
The renderer is a thin module:
|
||
|
|
|
||
|
|
```python
|
||
|
|
# src/atocore/mirror/renderer.py (V1, not yet implemented)
|
||
|
|
|
||
|
|
def render_project_overview(project: str) -> str:
|
||
|
|
"""Generate the project overview markdown for one project."""
|
||
|
|
facts = collect_project_overview_facts(project)
|
||
|
|
template = load_template("project-overview.md.j2")
|
||
|
|
return template.render(**facts)
|
||
|
|
```
|
||
|
|
|
||
|
|
## The "do not edit" header
|
||
|
|
|
||
|
|
Every generated Mirror file starts with a fixed banner:
|
||
|
|
|
||
|
|
```markdown
|
||
|
|
<!--
|
||
|
|
This file is generated by AtoCore from current entity state.
|
||
|
|
DO NOT EDIT — manual changes will be silently overwritten on
|
||
|
|
the next regeneration.
|
||
|
|
Edit the canonical home instead. See:
|
||
|
|
https://docs.atocore.../representation-authority.md
|
||
|
|
Regenerated: 2026-04-07T12:34:56Z
|
||
|
|
Source entities: <commit-like checksum of input data>
|
||
|
|
-->
|
||
|
|
```
|
||
|
|
|
||
|
|
The checksum at the end lets the renderer skip work when nothing
|
||
|
|
relevant has changed since the last regeneration. If the inputs
|
||
|
|
match the previous run's checksum, the existing file is left
|
||
|
|
untouched.
|
||
|
|
|
||
|
|
## Conflicts in the Mirror
|
||
|
|
|
||
|
|
Per the conflict model, any open conflict on a fact that appears
|
||
|
|
in the Mirror gets a visible disputed marker:
|
||
|
|
|
||
|
|
```markdown
|
||
|
|
- Lateral support material: **GF-PTFE** ⚠ disputed
|
||
|
|
- The KB-CAD import on 2026-04-07 reported PEEK; conflict #c-039.
|
||
|
|
```
|
||
|
|
|
||
|
|
The disputed marker is a hyperlink (in renderer terms; the markdown
|
||
|
|
output is a relative link) to the conflict detail page in the API
|
||
|
|
or to the conflict id for direct lookup. The reviewer follows the
|
||
|
|
link, resolves the conflict via `POST /conflicts/{id}/resolve`,
|
||
|
|
and on the next regeneration the marker disappears.
|
||
|
|
|
||
|
|
## Project-state overrides in the Mirror
|
||
|
|
|
||
|
|
When a Mirror page would show a value derived from entities, but
|
||
|
|
project_state has an override on the same key, **the Mirror shows
|
||
|
|
the project_state value** with a small annotation noting the
|
||
|
|
override:
|
||
|
|
|
||
|
|
```markdown
|
||
|
|
- Next focus: **Wave 2 trusted-operational ingestion** (curated)
|
||
|
|
```
|
||
|
|
|
||
|
|
The `(curated)` annotation tells the reader "this is from the
|
||
|
|
trusted-state Layer 3, not from extracted entities". This makes
|
||
|
|
the trust hierarchy visible in the human reading experience.
|
||
|
|
|
||
|
|
## The "Mirror diff" workflow (post-V1, but designed for)
|
||
|
|
|
||
|
|
A common workflow after V1 ships will be:
|
||
|
|
|
||
|
|
1. Reviewer has curated some new entities
|
||
|
|
2. They want to see "what changed in the Mirror as a result"
|
||
|
|
3. They want to share that diff with someone else as evidence
|
||
|
|
|
||
|
|
To support this, the Mirror generator writes its output
|
||
|
|
deterministically (sorted iteration, stable timestamp formatting)
|
||
|
|
so a `git diff` between two regenerated states is meaningful.
|
||
|
|
|
||
|
|
V1 doesn't add an explicit "diff between two Mirror snapshots"
|
||
|
|
endpoint — that's deferred. But the deterministic-output
|
||
|
|
property is a V1 requirement so future diffing works without
|
||
|
|
re-renderer-design work.
|
||
|
|
|
||
|
|
## What the Mirror enables
|
||
|
|
|
||
|
|
With the Mirror in place:
|
||
|
|
|
||
|
|
- **OpenClaw can read project state in human form.** The
|
||
|
|
read-only AtoCore helper skill on the T420 already calls
|
||
|
|
`/context/build`; in V1 it gains the option to call
|
||
|
|
`/mirror/{project}/overview` to get a fully-rendered markdown
|
||
|
|
page instead of just retrieved chunks. This is much faster
|
||
|
|
than crawling individual entities for general questions.
|
||
|
|
- **The human gets a daily-readable artifact.** Every morning,
|
||
|
|
Antoine can `cat /srv/storage/atocore/data/mirror/p05/overview.md`
|
||
|
|
and see the current state of p05 in his preferred reading
|
||
|
|
format. No API calls, no JSON parsing.
|
||
|
|
- **Cross-collaborator sharing.** If you ever want to send
|
||
|
|
someone a project overview without giving them AtoCore access,
|
||
|
|
the Mirror file is a self-contained markdown document they can
|
||
|
|
read in any markdown viewer.
|
||
|
|
- **Claude Code integration.** A future
|
||
|
|
`/atocore-mirror <project>` slash command renders the Mirror
|
||
|
|
inline, complementing the existing `/atocore-context` command
|
||
|
|
with a human-readable view of "what does AtoCore think about
|
||
|
|
this project right now".
|
||
|
|
|
||
|
|
## Open questions for V1 implementation
|
||
|
|
|
||
|
|
1. **What's the regeneration debounce window?** 30 seconds is the
|
||
|
|
starting value but should be tuned with real usage.
|
||
|
|
2. **Does the daily refresh need a separate trigger mechanism, or
|
||
|
|
is it just a long-period entry in the same in-process scheduler
|
||
|
|
that handles the debounced async refreshes?** Probably the
|
||
|
|
latter — keep it simple.
|
||
|
|
3. **How are templates tested?** Likely a small set of fixture
|
||
|
|
project states + golden output files, with a single test that
|
||
|
|
asserts `render(fixture) == golden`. Updating golden files is
|
||
|
|
a normal part of template work.
|
||
|
|
4. **Are Mirror pages discoverable via a directory listing
|
||
|
|
endpoint?** `GET /mirror/{project}` returns the list of
|
||
|
|
available pages for that project. Probably yes; cheap to add.
|
||
|
|
5. **How does the Mirror handle a project that has zero entities
|
||
|
|
yet?** Render an empty-state page that says "no curated facts
|
||
|
|
yet — add some via /memory or /entities/Decision". Better than
|
||
|
|
a blank file.
|
||
|
|
|
||
|
|
## TL;DR
|
||
|
|
|
||
|
|
- The Human Mirror generates 3 template families per project
|
||
|
|
(Overview, Decision Log, Subsystem Detail) from current entity
|
||
|
|
state
|
||
|
|
- It's strictly read-only from the human's perspective; edits go
|
||
|
|
to the canonical home and the Mirror picks them up on
|
||
|
|
regeneration
|
||
|
|
- Three regeneration triggers: explicit POST, debounced
|
||
|
|
async-on-write, daily scheduled refresh
|
||
|
|
- Mirror files live in `/srv/storage/atocore/data/mirror/`
|
||
|
|
(NOT in the source vault — sources stay read-only)
|
||
|
|
- Conflicts and project_state overrides are visible inline in
|
||
|
|
the rendered markdown so the trust hierarchy shows through
|
||
|
|
- Templates are checked into the repo and edited via PR; the
|
||
|
|
rendered files are derived and never canonical
|
||
|
|
- Deterministic output is a V1 requirement so future diffing
|
||
|
|
works without rework
|