Files
chemenu/docs/knowledge-and-commitment.md
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

9.9 KiB

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 and the review, new and task new records in tools/CONTRACT.md.

Contents

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.