Skip to content

Superself 0.12.0: a work unit declares what done means

Choose a tag to compare

@tonite31 tonite31 released this 28 Aug 10:02
· 18 commits to main since this release
8b5dbb7

Superself 0.12.0 lets a unit of work carry its own completion conditions. A unit used to state one outcome and one judgment — done, evidenced by a report — and everything between them lived in a session's head or in prose nobody could address. This release gives that middle a grammar: ordered criteria addressed c1..cN, each with a recorded verification method, each covered one at a time, one of them able to wait on something without stopping the unit around it, and one of them able to belong to a person rather than to the session that wrote it down. work done is refused until every one is covered. Around that: a milestone page that reads its linked units instead of naming their ids, an artifact that can be a URL, and self log finally saying who wrote each event.

A unit declares its criteria, and is not done until each is covered

--criteria is repeatable and ordered; --verify says how one of them is checked, keyed by its position. The verification text is recorded prose — nothing here executes it, ever.

self work add "<outcome>" [--criteria "<text>" …] [--verify "cN <how it is checked>"] [--owner "cN person"]
self work criteria add <id> "<text>" [--verify "<how it is checked>"] [--owner person]
self work cover  <id> --criterion cN --why "<how it is covered>" [--evidence <commit>] [--work <id>]
self work block  <id> [--criterion cN] --on decision|dependency|external [--why w]
self work unblock <id> [--criterion cN]
$ self work add "the release notes state what 0.12.0 changed" --criteria "every command line in the notes is run against the built CLI" --criteria "the mixed-version section is read off the fold" --criteria "a maintainer approves the wording" --verify "c1 the pasted output is a real run" --verify "c2 the older CLI is checked out and folded" --owner "c3 person"
entity.confirmed recorded [01m13w9jyyxy0sx6ss10pnemhp]
  w-f4v81 the release notes state what 0.12.0 changed — verify; wrong? self undo 01m13w9jyyxy0sx6ss10pnemhp
w-f4v81

Covering one is a claim with a reason, and blocking one names what it waits on. Each is its own event, so self undo takes back exactly the one it names:

$ self work cover w-f4v81 --criterion c1 --why "every line in the notes was pasted from a run"
entity.covered recorded [01m13w9q7q2rfkc3hbtdbf6zyh]
  w-f4v81 every command line in the notes is run against the built CLI every line… — verify; wrong? self undo 01m13w9q7q2rfkc3hbtdbf6zyh

$ self work block w-f4v81 --criterion c2 --on dependency --why "the 0.11.0 tree has to be built first"
entity.criterion-blocked recorded [01m13w9qc7kcp1nsmd2k6vd00n]
  w-f4v81 the mixed-version section is read off the fold — verify; wrong? self undo 01m13w9qc7kcp1nsmd2k6vd00n

The unit's page carries a - Criteria: block, each row naming the criterion's own words and then how it is checked. A blocked criterion never changes the unit's own status — the unit below is next, not blocked, which is the whole reason a criterion's block is its own event type rather than a field on entity.blocked:

$ self work show w-f4v81
# w-f4v81 — the release notes state what 0.12.0 changed

- Status: next
- Criteria: 1 of 3 covered (1 blocked)
  - c1 covered — every line in the notes was pasted from a run (agent 2026-08-28)
  - c2 blocked on dependency — the 0.11.0 tree has to be built first
  - c3 open — a maintainer approves the wording (person)
- Not done yet: w-f4v81 declares 3 criteria and 2 are not covered:
    c2  blocked on dependency — the 0.11.0 tree has to be built first
    c3  open — a maintainer approves the wording (person)
  cover each with `self work cover w-f4v81 --criterion c3 --why "<how>"` — a covered criterion's block ends with it — or retire the unit if the outcome was given up

work done is refused with that same list, so the refusal is the page's own sentence rather than a second wording of it. The cover each with … line it hands back prefers a criterion that is neither blocked nor somebody else's, so the pasted command is usually one this session can run outright; when every remaining criterion is one or the other, as here, it names one rather than naming none:

$ self work done w-f4v81 --report "the notes were written and every command run"
error: w-f4v81 declares 3 criteria and 2 are not covered:
    c2  blocked on dependency — the 0.11.0 tree has to be built first
    c3  open — a maintainer approves the wording (person)
  cover each with `self work cover w-f4v81 --criterion c3 --why "<how>"` — a covered criterion's block ends with it — or retire the unit if the outcome was given up

k of n is one sentence, composed once and read by the four surfaces that mention a unit — its own page above, the listing, self context, and milestone show's linked work — so none of them can describe one unit differently. The listing:

$ self work
w-f4v81  next  the release notes state what 0.12.0 changed  [1 of 3 criteria covered]
1 open work unit

and the work-in-progress row of self context, which carries the blocked criterion and the owned one by name:

$ self context
## Work in progress

- w-f4v81 the release notes state what 0.12.0 changed — 1 of 3 criteria covered · c2 blocked on dependency · c3 (person)

A criterion a person owns waits on them

--owner "cN person" on work add and work propose, or --owner person on work criteria add, says the criterion is somebody else's task rather than this session's. An open, person-owned criterion on an open unit lists under Waiting on you with the command that closes it:

$ self context
## Waiting on you

- w-f4v81 c3 a maintainer approves the wording — yours (cover with `self work cover w-f4v81 --criterion c3 --why "<how>"`)

One row per uncovered person-owned criterion, in cN order — two on one unit are two things to do, and a merged row would carry two --criterion values in one command. A proposal's owned criteria do not list: the review already has a row of its own, and asking somebody to cover a criterion of a plan they have not accepted asks for the wrong act. Ownership marks the criterion (person) wherever criteria render, and changes nothing else — not the unit's status, not its blocked count, not k of n.

The one-sentence distinction, carried on self help work and in the agent instruction block: by is who wrote the record; --owner is whose task the criterion is — a session records a criterion a person will do, and neither implies the other.

milestone show reads its linked units

The page printed the exit criteria, the coverage claims and one line naming the linked ids, so finding out where any of them stood meant running self work show once per id. A ## Linked work section now answers that on the page, between ## Exit criteria and ## Coverage:

$ self milestone show m-yn1ac
# m-yn1ac — the release PR is ready to merge

- Objective: o-4d5z7 0.12.0 ships with notes a reader can act on
- State: on-track — 0 of 2 exit criteria covered across 2 work unit(s)
- Revision: 1 (objective revision 1)
- Critical path: no
- Work: w-f4v81, w-bf87w

## Exit criteria

- c1 — the notes are written _(open)_
- c2 — the gates are green _(open)_

## Linked work

- **w-f4v81** the release notes state what 0.12.0 changed — active, 1 of 3 criteria covered · c2 blocked on dependency · c3 (person)
  - report 2026-08-28: the criteria render was checked on all four surfaces
- **w-bf87w** the golden fixture carries the new version — next, 0 of 1 criteria covered

Each entry is the unit's standing, then its k of n sentence when the unit declares criteria, then who holds it when a session does, then its latest report's first line beneath. Units appear in the milestone's own record order. The section is on self milestone show alone: it is read at command time, and the holder clause is this machine's judgment of another machine's session, so objective show and the folded objective/<id>.md that syncs to every clone are byte-identical to before.

An artifact can be a URL, with a kind, attached to a unit or a milestone

self artifact add <path|url> [--kind brief|pr|resource|doc] [--for <work-id|milestone-id>]

A URL is recorded as a link. No bytes are stored and nothing is fetched — not at add, not at open, not by the health check:

$ self artifact add https://github.com/fxylabs/superself/pull/412 --kind pr --for w-f4v81
artifact.linked recorded [01m13wadhae6p0z4121bfr5z2e]
  a-3afgk https://github.com/fxylabs/superself/pull/412 — verify; wrong? self undo 01m13wadhae6p0z4121bfr5z2e
a-3afgk

A path keeps today's behaviour and takes the same two flags. Both shapes list, search and filter alike, with the kind in the row:

$ self artifact list
a-3afgk  2026-08-28  demo  w-f4v81  https://github.com/fxylabs/superself/pull/412 [pr]
a-h07g4  2026-08-28  demo  -  https://github.com/fxylabs/superself/issues/415 [brief]
a-0mca6  2026-08-28  demo  w-f4v81  draft.md [doc]
a-8fd6r  2026-08-28  demo  w-f4v81  https://example.com/res [resource]
a-bjfms  2026-08-28  demo  w-f4v81  https://example.com/brief [brief]
5 artifacts

The work column is the unit a --for attached it to, and - where nothing did. a-0mca6 is a path: bytes copied into the store, listed beside the links with no distinction a reader has to make.

--for attaches either shape to a work unit or a milestone, and both pages then list what is attached, ordered brief, pr, resource, doc:

$ self work show w-f4v81
## Attached artifacts

- a-bjfms  brief  https://example.com/brief
- a-3afgk  pr  https://github.com/fxylabs/superself/pull/412
- a-8fd6r  resource  https://example.com/res
- a-0mca6  doc  draft.md
$ self milestone show m-yn1ac
## Attached artifacts

- a-h07g4  brief  https://github.com/fxylabs/superself/issues/415

artifact open on a link prints the address and launches nothing — a log travels between machines, and handing an address out of one to the OS launcher is trust the CLI already refuses to extend to a recorded path:

$ self artifact open a-3afgk
https://github.com/fxylabs/superself/pull/412 — a-3afgk is a link, so nothing was fetched and nothing was launched

An attachment is not evidence: it never satisfies work done, which reads a unit's reports and now its criteria, and nothing here writes either.

self log says who wrote an event

Since 0.11.0 every event an undoable verb writes carries by — a person at a terminal, or an agent session. <verb> show <id> --history rendered it and self log did not, so the one surface that lists events was the one surface that would not say who wrote them.

This reverses what v0.11.0's notes said. Those notes stated that "self log does not carry the column. It is the workspace timeline and a machine contract, and a column every reader of it has to skip is one it does not owe." The ruling that replaces it is that by is the audit trail 0.11.0 introduced, and an audit trail absent from the one surface that lists events is not one. The note is now a suffix on the row, after the id and after the · undone mark:

$ self log -n 4
2026-08-28T09:48:21.930Z  artifact.linked  [01m13wadhae6p0z4121bfr5z2e]  
2026-08-28T09:48:22.059Z  artifact.linked  [01m13wadnb22c3cjrwrdj3pq42]  
2026-08-28T09:48:39.702Z  entity.confirmed  [01m13waywps9k2kcdzw05bxaeg]  the notes are reviewed by a person before merge · by person
2026-08-28T09:48:49.691Z  entity.confirmed  [01m13wb8mveemsnsafwcyx6zg1]  the release notes ship with the version bump · by agent (session a1b2c3d4)
last 4 of 15 events · self log -n 15 --project 'demo'

The columns are otherwise untouched, and self log -n N still prints N rows and one size line. The suffix is appended where the row is built rather than by each of its three callers — self log, self log --workspace and a record's own history page — which is how the omission happened and what stops a fourth surface repeating it. At a terminal the note is dimmed and its width is charged to the summary's fit budget rather than added past it; piped, it is plain text with no escape sequence, so a machine reading the contract reads words rather than parsing past a colour code. An event carrying no by — every record written before 0.11.0, and the two artifact.linked rows above — prints its row and says nothing about a writer, rather than being read as a person.

A runbook is not a unit's criteria

Declared criteria look enough like a runbook's stages that the boundary is stated as one paragraph, carried verbatim on self work --help / self help work and again on self help runbook, so the two ideas cannot drift into two rules read from two directions:

a runbook is a procedure this project repeats — registered once, run per
piece of work, with the same stages every run. A work unit's criteria are
that one unit's completion conditions: declared on it, judged on it, never
run again. If you would declare the same list on the next unit too, it is a
runbook.

Bounds and refusals

  • No criterion is ever removed. There is no delete verb. A mistaken declaration is taken back with self undo, and the criteria after it renumber; one that is no longer needed is covered with a reason and no evidence.

    $ self undo 01m13wf2tn3n2r02a23p4s48ts
    entity.annulled recorded [01m13wf31jg1b31w8ftnv1hnhr]
    w-bf87w no longer declares "a maintainer signs off on the golden diff" — the declaration was taken back; the criteria after it renumber
    
  • Ownership is stated at declaration and never re-stated. --owner is on work add, work propose and work criteria add, and on no other verb — the row it produces carries a self work cover line, and that line needs a unit. Re-owning a criterion is self undo and a fresh declaration.

    $ self work cover w-f4v81 --criterion c3 --owner person --why x
    error: unknown option '--owner' — run `self work --help` for the syntax
    
  • person is the only owner value, refused at the flag, and one a later CLI writes that this one does not know is ignored rather than carried — a yours row about an owner it cannot name would be worse than no row.

    $ self work add "x" --criteria "a" --owner "c1 agent"
    error: work add --owner takes "person" — it is the only owner a criterion states, and one with no owner is the session's own task
    
  • A URL artifact is never fetched. Not at add, not at open, not by the health check. Only http and https are links; every other scheme names bytes somewhere and is refused with the path to pass instead, and an address carrying a username or password is refused outright, because the log is committed and pulled by every clone.

    $ self artifact add ftp://example.com/notes.pdf --kind doc
    error: artifact "ftp://example.com/notes.pdf" is not http or https, and a link is one of those — every other scheme names bytes somewhere, so pass the path they are at and the store keeps a copy of them
    
    $ self artifact add https://user:secret@example.com/x --kind doc
    error: a link carrying a username or password is refused — the log is committed and pulled by every clone of this store, so record the address without them
    
  • artifact prune on a link is refused by name. There are no bytes to remove, so the refusal names the self undo that takes the record back instead. prune keeps the person-at-a-terminal gate it kept in 0.11.0, and it is still the only verb that has one.

    $ self artifact prune a-3afgk --why "wrong pr"
    error: a-3afgk is a link to https://github.com/fxylabs/superself/pull/412 — this store holds no bytes for it, so there is nothing to prune; take the record back with `self undo 01m13wadhae6p0z4121bfr5z2e`
    
  • A link is invisible to every byte-counting reader. store size and the dedupe of stored bytes ignore it: both ask a question about bytes a link does not have, and counting one would raise the distinct-contents count with every URL. Five artifacts are recorded in the store above and four of them are links:

    $ self store size
    store 308.8 KB — working tree 205.6 KB, .git 103.3 KB
    artifacts: 1 recorded, 1 distinct contents, 12 B in 1 stored file
    
  • --verify is recorded prose and is never executed, on any path, ever. It says how a criterion is checked; a person or a session does the checking.

  • A criterion's block never moves the unit. entity.criterion-blocked and -unblocked are separate types precisely so a unit's own working state is untouched, and so an older CLI cannot fold one into a unit-level unblock a person recorded.

  • An attachment is not evidence. --for never satisfies work done, which reads reports and criteria.

  • Attribution answers cooperating callers, exactly as it did in 0.11.0. Nothing here is a signature, an allowlist, or an identity check, and the writer note in self log renders what the record says rather than verifying it.

Mixed versions: a 0.11.0 CLI reading a store written here

Read off the fold rather than assumed: the v0.11.0 tree was checked out, built, and pointed at a store this release's verbs wrote. status, context, work, work show, state, state show, state show --history, log, artifact list, artifact list --work, objective, milestone, milestone show, search, store size and lang each answered with exit 0. One command refused, and it is in the table.

What the store carries 0.11.0 Why
payload.criteria on a creation event read — state show prints one criterion: line each the field is 0.11.0's own; only the work show render and the work done gate are new
payload.verify on a creation event ignored — the criteria read, their verification methods do not newEntity reads named keys only
payload.owner on a creation event, or on entity.criterion-declared ignored — no (person) mark, no Waiting on you section an unread key on a payload, riding an event 0.11.0 already ignores whole
entity.criterion-declared ignored — the unit reads with its birth criteria alone, and self log still lists the row by type no collector matches the type, so the fold applies nothing
entity.criterion-blocked / entity.criterion-unblocked ignored — the criterion reads open and the unit's own status is untouched the reason these are not entity.blocked {criterion}, which 0.11.0 would have folded into a unit-level block, and whose unblock would have cleared one a person recorded
entity.covered written by work cover read — folded normally, byte-identical to state cover's this direction loses nothing at all
artifact.linked ignored — no registry row, no store size count, no health line, no crash, no refusal; the row still lists in self log by type declaredArtifacts returns nothing for a type it does not name
artifact open <link-id> on 0.11.0 refused — error: unknown artifact "a-3afgk" — run 'self artifact list' to see ids the only refusal found; the link is not in its registry, so it refuses an id it cannot see rather than crashing on one it can
artifact.registered with payload.kind read, minus the kind — the row lists as it always did, without the [doc] mark the artifact meta inside payload.artifacts is unchanged, and an unknown payload key is read by nothing
artifact.registered with refs.work (a path attached with --for) read — the work id is in its work column, and artifact list --work finds it refs.work is the field 0.11.0 already derives record.work from
payload.by read as it always was on a record's history page; not rendered in self log 0.11.0's own field. The log suffix is a render change in this release, not a store change, so nothing about it travels or fails to travel
the ## Linked work section not applicable — a render read at command time, never folded, never written to a page that syncs a 0.11.0 clone simply prints the milestone page it always printed

Side by side on the same store, self context shows exactly what is lost and nothing else:

# 0.12.0
- w-f4v81 the release notes state what 0.12.0 changed [toward m-yn1ac] — 1 of 3 criteria covered · c2 blocked on dependency · c3 (person) — latest report: …

## Waiting on you

- w-f4v81 c3 a maintainer approves the wording — yours (cover with `self work cover w-f4v81 --criterion c3 --why "<how>"`)
# 0.11.0, same store
- w-f4v81 the release notes state what 0.12.0 changed [toward m-yn1ac] — latest report: …

(no Waiting on you section)

One consequence to know before you mix versions. The criteria half of the work done gate is new here, so a 0.11.0 CLI admits a completion this one refuses. Run against the same unit above — three criteria, two uncovered — 0.11.0 recorded the done:

$ self work done w-f4v81 --report "the notes were written and every command run"   # 0.11.0
report.added recorded [01m13wcc010e8atyavq1rwx9h6]
  w-f4v81 the release notes state what 0.12.0 changed — verify; wrong? self undo 01m13wcc010e8atyavq1rwx9h6
entity.done recorded [01m13wcc02eg5e8m8deek8dnca]
  w-f4v81 the release notes state what 0.12.0 changed — verify; wrong? self undo 01m13wcc02eg5e8m8deek8dnca

The older gate is looser and never tighter: it never refuses a done this CLI would allow. self undo takes such a done back from either version.

In the other direction nothing is lost. A store written by 0.11.0 reads here exactly as it did there: a unit declares what its creation payload declared and is gated on that, a unit with no criteria is gated on none, and a record with no by says nothing about a writer. No migration, no back-fill, no new refs key.

Upgrade after publication

After the v0.12.0 release workflow finishes, install the published package:

npm install -g superself@0.12.0

Evidence

  • PR #409 makes self log say who wrote an event, reversing the ruling v0.11.0's notes stated and appending the note where the row is built rather than per caller. Case table.
  • PR #410 gives self milestone show a ## Linked work section that reads each linked unit's standing, criteria, holder and latest report. Case table.
  • PR #411 lets an artifact be a URL with a kind, attached to a unit or a milestone — no bytes stored and nothing ever fetched. Case table.
  • PR #412 gives a work unit ordered criteria with a verification method, a criterion that blocks on its own, and the k of n sentence four surfaces share. Case table.
  • PR #414 makes a criterion a person owns wait on them under Waiting on you, and states the by versus --owner distinction wherever both appear. Case table.
  • Release policy defines the version, tag, and publication path.