Files
chemenu/docs/knowledge-and-commitment.md
T
torben b79f083cc1
CI / verify (push) Failing after 1m3s
docs: README/INSTALL/DEVELOPMENT/docs point at -h and command records, not the old tables (#121)
Files changed:
- DEVELOPMENT.md
- INSTALL.md
- README.md
- docs/knowledge-and-commitment.md
2026-09-26 07:54:12 +02:00

148 lines
9.9 KiB
Markdown

# Why knowledge and commitments are two layers
Chemenu compiles knowledge into `kb/`, and it also tracks what its operator has committed to do.
Those look like one subject - both are "things about my projects" - and the stack deliberately
keeps them apart: `kb/gtd/` holds one page per initiative, an external task tracker holds the
open items, and the only thing that crosses between them is a name. This page is about why that
line was drawn there. The rules that follow from it live in [kb/CONTRACT.md](../kb/CONTRACT.md)
and the `review`, `new` and `task new` records in [tools/CONTRACT.md](../tools/CONTRACT.md).
<!-- wikitool:toc -->
## Contents
- [Different half-lives want different machinery](#different-half-lives-want-different-machinery)
- [Pattern 4: separate ownership, no synchronization](#pattern-4-separate-ownership-no-synchronization)
- [The join happens at read time, and stores nothing](#the-join-happens-at-read-time-and-stores-nothing)
- [One name, carrying the duties of an identifier](#one-name-carrying-the-duties-of-an-identifier)
- [Status has exactly one home](#status-has-exactly-one-home)
- [A finished initiative is a state, not a location](#a-finished-initiative-is-a-state-not-a-location)
- [Which tracker is a decision the stack does not make](#which-tracker-is-a-decision-the-stack-does-not-make)
<!-- /wikitool:toc -->
## Different half-lives want different machinery
`kb/` is a compiler for durable things, and every mechanism in it assumes durability: `raw/` is
immutable, a claim has to trace back to a source, a page's title is its identity, the indexes are
generated, and a large change stops at a gate so a human can look at it. All of that is the right
amount of ceremony for something that will still be true next year.
A next action is the opposite kind of fact. It is unsourced - nobody cites a reason for "call the
plumber". It changes several times a week. It is state, not knowledge: the interesting thing
about it is whether it is still open. And it is only correct *now*.
Running both through one layer does not produce a richer wiki; it produces a worse one. Every
task-shaped page carries `provenance: general` because there is no source to bind it to, which
drains that field of meaning for the pages where it matters. `kb/log.md` fills with "task
checked off" entries until the audit trail of what the *wiki* learned is unreadable. Lint findings
about orphans and stale claims start firing on pages that are supposed to be short-lived. And a
weekly pass over the task list trips the Mass-Update Gate every single time, which is how a gate
stops being read and starts being cleared reflexively.
The GTD method this borrows from draws the same line for its own reasons: of its horizons, `kb/`
covers the two slowest - project support material and reference - and nothing faster.
## Pattern 4: separate ownership, no synchronization
Four arrangements were on the table, and three of them fail in ways worth naming.
**One layer** is the case above. **Export** - the wiki writes a task list the tracker imports -
means a checkbox ticked in the tracker is a tick in a view, while the truth sits in a file the
operator was not editing; the two disagree immediately and silently. **Bidirectional sync** works,
at the cost of an id mapping to maintain, a conflict-resolution rule to design, and a deletion
semantics to decide - all of it machinery whose only job is to repair a split nobody needed.
What is left is **separate ownership with no sync at all**: the tracker owns the tasks, `kb/` owns
the project memory, and the single point of contact is the project's name. Nothing is mirrored,
so nothing can drift out of mirror.
## The join happens at read time, and stores nothing
Because there is no shared state, the connection between the two sides has to be made when
somebody actually asks - which is what `wikitool review` does: it reads both sides, matches them on
the case-normalized project name, prints what it found, and saves nothing. Not a cache, not a
mapping file, not even a `reports/` artifact.
That is the same posture `search` takes, and for the same reason: anything it wrote down would be
a third copy of a state the two sides already hold, stale the moment either side moved, and the
first thing to distrust in a report. A read-time join can be wrong about the present, but it
cannot be wrong about the past, because it does not remember one.
## One name, carrying the duties of an identifier
Reducing the coupling to a name is cheap, and it is not free. A name that joins two systems is an
identifier, whether or not anything enforces it, so the design had to pick up an identifier's
obligations explicitly: uniqueness is checked before a project is created rather than discovered
later; a rename is a deliberate, infrequent operation that touches both sides in one pass; and
nothing tries to re-match automatically behind the operator's back.
The last one is what makes the review's *both-directional* report matter. A tracker project with
no page and a page with no tracker project are reported separately, as two findings. They are
usually the two halves of one rename - and reporting them separately is exactly what turns a
silent decoupling into a visible event, at the cost of the review occasionally saying the same
thing twice.
## Status has exactly one home
The sharpest consequence of the split is a rule that feels like a restriction: a `kb/` page never
summarizes its own task list. No "3 open items", no "next: call the supplier".
Two places claiming to know the current status is the failure mode the whole arrangement exists
to avoid, and a summary is a copy with a slower clock. The page says what an initiative *is* -
its goal, its participants, its durable state, why it is worth doing. The tracker says what is
open right now. Anyone wanting the second reads the tracker, or runs the review.
This pays for itself somewhere unexpected: with the page carrying no task state, an agent has no
reason to read the task list at all outside the weekly review. That is what keeps the command
surface as small as it is - two read commands and three write commands - rather than growing a
full CRUD tree over somebody's todo list. The second creation command exists because a single
name is not always the whole story: a source can carry a piece of durable knowledge and a
commitment to follow up on it at the same time - a complaint arriving by email is both something
to file and something to chase - and the tracker-side half of that needs its own write path
alongside `new project`'s pairing of a page with a tracker project. `task new` creates only the
tracker item, never a page; a source that also carries knowledge gets that knowledge filed
through the ordinary page-creation commands, as a separate step. The two are never one
transaction the way `new project`'s tracker-then-page order is within a single command - they are
two independent writes a skill sequences, tracker first, so a failure creating the item leaves no
page and no promoted source material behind it. That holds for an ordinary source; a source large
or broad enough to run through the large-tree procedure instead promotes ahead of its own
per-unit commitment decision, because that procedure hands its units through a workshop directory
that needs them already promoted to address them at all - the same raw-file-without-page state
the ordinary case avoids becomes, there, the expected condition for as long as the run takes. And
a failure on the knowledge side afterwards is exactly the ordinary "a source without a page" state
`lint` already reports.
The write surface stops at *creating* an item and *marking one done* - it never moves a reminder
and never deletes anything. `task close` sets exactly the field the tracker's own "done" checkbox
sets, nothing more: reversible, and it leaves a record in the tracker rather than removing the
item's trace. A command that deleted would take the same shortcut through somebody's task list
that the whole split above exists to avoid - a write this stack cannot undo, made on behalf of a
tracker it does not own. `task list` is the one addition on the read side, and it changes nothing
about the join itself: it exists only because closing an item needs the tracker's own id for it,
and that id was never worth exposing before there was a write that consumed it.
## A finished initiative is a state, not a location
Archiving moves nothing. A completed initiative's page stays where it is and changes its `state:`
value, because the moment an initiative finishes is the moment its page is *most* valuable -
what was decided, what it cost, who was involved - and filing it away is how that gets lost.
The state field carries the distinction the review actually needs, which is not "open vs. done"
but "does silence here mean something is wrong". An initiative that is deliberately paused looks
identical, from the outside, to one that quietly stalled; only the operator knows which. Without a
value for "paused on purpose", the review reports the same untouched initiatives every week, and
a report that is mostly noise stops being read by the third week - which would cost more than the
findings are worth.
## Which tracker is a decision the stack does not make
The tracker is reached through a provider layer, and no instruction anywhere names which one it
is. An instruction that said "open Super Productivity" would bake one instance's tool choice into
the shared stack, and the next instance - a different context, a different employer, a different
set of constraints - would have to edit prose to change a setting.
So the provider lives in configuration (`.wikitool-tasks.json`), the adapters live behind one
protocol, and a capability the provider lacks surfaces as an ordinary tool error rather than as a
paragraph of instruction explaining what this particular tracker cannot do. A provider that
cannot create a project, for instance, stops and asks the operator to do it - the same posture the
gates take, and for the same reason: better a visible stop than an invented workaround.