ticketing · diff

git:20260824.895c291 to git:20260909.eeacd53

18 added, 13 removed. Audit A to A.

---
name: ticketing
description: Local-private task/ticket management for the whiteboard repo. Issues and notes live in the whiteboard itself (via MCP tools) as OKF Markdown documents. The native Task list tracks in-flight session work. Use when triaging findings, tracking work across dev-loop/review/dogfood-triage/reconcile/plan-initiative, or recording/resolving a follow-up.
---
# Ticketing (local-private)
We deliberately do NOT use GitHub Issues. Two layers + a bridge:
## Two layers
| Layer | What | Lifetime |
|---|---|---|
| **Native Task list** (`TaskCreate`/`TaskList`/`TaskUpdate`/`TaskGet`) | the LIVE board: what is running / blocked / done across workflows this session | session/team-scoped |
| **Whiteboard documents** (MCP tools) | the DURABLE private backlog: issues & notes stored as OKF Markdown documents in the `default` workspace | persists on disk via the daemon |
The **main session (integrator / team-lead) owns Task status transitions**. Subagents may update `metadata` but not flip status to completed (mirrors the team workflow).
## Whiteboard as the issue store
Issues and notes are stored as documents in the whiteboard's `default` workspace. Each is an OKF Markdown document with:
- **type**: `issue` or `note`
- **title**: human-readable title
- **facets**: extension metadata. There is no agreed schema for issue metadata
right now — see "No structured status" below before inventing one
- **body**: the issue description as markdown
### Creating an issue
- One call. `kind` is required, and `markdown` at create time is what saves the
- separate `wb_document_set` this flow used to need.
+ One call. `kind` is required, and `markdown` on the create op is what saves the
+ separate `document.set` this flow used to need.
```
- wb_document_create → { workspaceId: "default", path: "issues/my-issue",
- kind: "markdown", name: "My Issue",
- markdown: "---\ntype: issue\ntitle: My Issue\n---\n\nDescription here." }
+ wb_workspace_edit → { workspaceId: "default", ops: [
+ { op: "document.create", path: "issues/my-issue",
+ kind: "markdown", name: "My Issue",
+ markdown: "---\ntype: issue\ntitle: My Issue\n---\n\nDescription here." } ] }
```
- Several at once — seeding a triage pass, say — go through one
- `wb_workspace_edit` rather than one call per document.
+ Several at once — seeding a triage pass, say — are more ops in the same call,
+ not more calls. `wb_workspace_edit` is the ONLY tool for create / set / delete;
+ the standalone ones are retired.
### Reading issues
```
wb_document_list → { workspaceId: "default" } # every document, open and closed
wb_document_get → { workspaceId: "default", documentId } # read it as OKF markdown
```
### Updating an issue
Re-import with updated OKF markdown (overwrites facets + body):
```
- wb_document_set → { workspaceId: "default", documentId, markdown: "<updated OKF>" }
+ wb_workspace_edit → { workspaceId: "default",
+ ops: [{ op: "document.set", documentId, markdown: "<updated OKF>" }] }
```
### Updating facets only
```
wb_facet_set → { workspaceId: "default", documentId, facets: { "<namespace>.<name>/v0": { … } } # key grammar per ADR-0013 }
```
### Resolving
**Resolution is `type: issue` -> `type: note`, not deletion.** Re-import the
document with the type changed, the name prefixed `RESOLVED — `, and a line
in the body saying what resolved it.
- **Read it first.** `wb_document_set` replaces core facets, extension facets and
+ **Read it first.** `document.set` replaces core facets, extension facets and
body together — it does not merge — so a resolution that names only `type` and
`title` silently drops `tags`, `facets`, and any root key the document arrived
with (`status`, `sources`, `stale_after`, all preserved verbatim since
[ADR-0016](../../../docs/contributing/adr/0016-okf-trust-family.md)). Under the
old convention that cost nothing, because the document was about to be deleted.
Now it is the whole point.
```
wb_document_get → { workspaceId: "default", documentId }
# take the returned markdown, change ONLY these, keep everything else:
# type: issue -> note
# title: prefixed "RESOLVED — "
# body: a line at the top saying what closed it
- wb_document_set → { workspaceId: "default", documentId, markdown: <the edited whole> }
+ wb_workspace_edit → { workspaceId: "default",
+ ops: [{ op: "document.set", documentId, markdown: <the edited whole> }] }
```
Both halves earn their place, and neither invents a schema:
- `type` is core OKF and already means this. A resolved issue stopped being a
work item and became a record, which is what a note is. `type: issue` stays
the query for "what is open" — the same thing that identified an issue
before.
- The `RESOLVED — ` name prefix is what a reader sees. `wb_document_list`
returns `documentId`, `path` and `name` and no frontmatter, so without it
telling an open issue from a closed one costs one `wb_document_get` per
document. Someone reached for this prefix by hand before it was written
down, which is how it got here. If `wb_document_list` ever reports `type`,
the prefix becomes redundant.
### Deleting
Deletion is for a document that was never worth keeping — a scratch probe, a
duplicate, a mistake. It is **not** how work is closed.
```
- wb_document_delete → { workspaceId: "default", documentId }
+ wb_workspace_edit → { workspaceId: "default",
+ ops: [{ op: "document.delete", documentId }] }
```
Resolution used to be deletion, and that was carried over from when issues
were markdown files under `tmp/` — where deleting the file was the only move
available. It was never a decision about this store, and it cost real work:
what an issue accumulates is measurements and refuted hypotheses, and deleting
it throws exactly those away while leaving nothing to stop the same issue
being filed and investigated again. Observed: one flake was investigated,
filed, deleted as resolved, and re-filed by another session within minutes,
its analysis redone from scratch.
### No structured status
The `issue/1` facet domain was retired: it was implemented by an earlier pass
without an agreed schema, and a typed companion nobody called made it look
more settled than it was. Nothing has replaced it, and nothing needs to for
open-vs-closed — that is what `type` above carries, using a core OKF field
rather than a domain of our own.
So an issue document carries `type`, a `title`, and a body — and no
machine-readable priority or assignee. There is no evidence either is needed:
one person works this backlog, and the Task list already carries in-flight
state.
Do not invent a replacement domain in passing. Extension facets round-trip
unvalidated through the generic bucket, so anything you write will persist and
quietly become the convention. Agree the schema first.
### Declaring what an issue is about
An issue MAY declare OKF `sources` — the files or documents its claims are
about. Nothing requires it, and a document without any is simply not judged.
```
sources:
- resource: apps/web/src/components/workspace-files/SearchResults.tsx
- resource: packages/server-core/src/tools/document-search.ts
```
`node .claude/scripts/stale-issues.mjs` then reports the open issues whose
sources have changed or been deleted since the document's own `generated.at`.
No new storage: `sources` rides the preserved-root-keys bucket, `generated.at`
is stamped by the write, and git already knows what moved. It also runs on
SessionStart, quietly, so a stale issue announces itself rather than waiting to
be re-read.
What it is worth, measured on the six issues one session read and found
already resolved: **four** named something that had since changed or been
deleted. The two it missed share a shape — the fix landed in a file the issue
never named — and that is the honest ceiling: `sources` says what a document is
ABOUT, not where a fix will land. It also cannot see the other failure at all,
a document that was simply wrong when written; the two most expensive
investigations in that session were of that kind.
So it reports "what this is about moved", never "this is resolved". Re-read
before closing anything.
### The other axis: is the write-up still true?
Open-vs-closed is about the WORK. Whether the document's own account is still
accurate is a separate question, and the two move independently — a closed
issue can hold a standing fact worth reading, and an open one can be built on
reasoning since disproved. Both happened in one session.
That axis is OKF v0.2's `status` (`draft` / `stable` / `deprecated`, absent
meaning `stable`), which this codebase preserves on documents that carry it.
Nothing produces or reads it yet, and it should stay that way until something
does — a signal nobody reads is inventory. Do NOT reach for `deprecated` to
mean "resolved": it says the account no longer holds, which would misreport
every closed issue whose write-up is still exactly right.
## tmp/ workspace buckets
Put temporary artifacts in the right bucket (never the root of `tmp/`):
- `tmp/issues/` — legacy issue source files (migrated to whiteboard documents)
- `tmp/notes/` — legacy design docs (migrated to whiteboard documents)
- `tmp/screenshots/` — UI captures during debug/verify
- `tmp/scripts/` — throwaway helper scripts
For **new** issues and notes, create them directly in the whiteboard via MCP tools.
Delete artifacts from `tmp/` when they're no longer useful.
## Bridge (Task list ⇄ whiteboard documents)
- **Session start**: `TaskList` to see live state; for open issues you intend to work this session, `TaskCreate` a task with `metadata.documentPath = "<path>"`.
- **In flight**: `TaskUpdate` status/owner/blockedBy as work moves.
- - **New finding** (from review / dogfood-triage / reconcile): create a document via `wb_document_create` + `wb_document_set`; `TaskCreate` only if you're acting on it now.
- - **Resolve**: `wb_document_set` the document to `type: note` FIRST (read it back first — see Resolving), then `TaskUpdate status=completed` once that write has succeeded. The other order leaves the Task list saying done while the issue is still open if the write fails. Do not delete the document.
+ - **New finding** (from review / dogfood-triage / reconcile): create a document via one `wb_workspace_edit` `document.create` op (the body rides on it); `TaskCreate` only if you're acting on it now.
+ - **Resolve**: a `document.set` op writes the document to `type: note` FIRST (read it back first — see Resolving), then `TaskUpdate status=completed` once that write has succeeded. The other order leaves the Task list saying done while the issue is still open if the write fails. Do not delete the document.
- Workflows can't call the Task tools or AskUserQuestion; they RETURN findings/openQuestions and the main session records them as tickets/tasks and asks the human.
## When to use which
- Orchestrating several workflow runs / parallel dev-loops right now → **Task list**.
- "Don't lose this for later" → **whiteboard document** (`type: issue`).
- Both, for anything you're actively working from the backlog (create the task, link the document path).