CI / verify (push) Failing after 1m3s
Files changed: - DEVELOPMENT.md - INSTALL.md - README.md - docs/knowledge-and-commitment.md
148 lines
9.9 KiB
Markdown
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.
|