feat(ctx): phase-2 supersede — correct an immutable fact via forward link#444
Merged
Conversation
…link ctx records are immutable on save, so a correction is a NEW fact whose `supersedes` points back at the one it replaces; the old record is never mutated. `ctx list` folds superseded facts out by default and gains `--all` to keep them (marked); `ctx show <old-id>` resolves the reverse `superseded_by` link at read time. Chains stay acyclic (links point strictly backward; domain rejects self-supersession); a missing target is a recoverable not-found. `supersedes` is omitted from ordinary records' YAML so phase-1 records round-trip byte-for-byte. Remaining phase-2 verbs: fork / chain / status.
…ead branch - supersededBy now returns ALL successors (newest-first), not the first: two independent corrections of one fact (a fork A<-B, A<-C) are both reported instead of silently picking one. JSON `superseded_by` is now a string[] (empty while current); text lists all. Cost is one substrate scan, inherent to the flat layout — documented as the spot a reverse index lands if `chain` pushes past the junk-drawer scale. - list: drop the unreachable 'all superseded' empty-message branch. The newest fact can never be superseded (nothing is written after it to point back), so a non-empty store always keeps a current head; an empty default list is genuinely empty. Removes a dead branch with a misleading message. - docs (verbs.md / AGENT.md) + changelog updated to the array contract, fork behavior, and the empty-store invariant. Adds a fork test.
Three duplications removed, behavior unchanged (full suite green): - appendFact() — the shared 'allocate id -> Ctx.create -> saveNew' write path. record() delegates directly; supersede() validates the target then delegates with the link. One write path, consistent id/timestamp pairing. - byNewestFirst() — the created_at-desc / id-desc comparator was copied in list() and supersededBy(); now one module helper both reuse (and a future verb like chain can share). - parseTagList() — the --tag splitter was copied verbatim in record.ts and supersede.ts; extracted to handlers/parseTagList.js, imported by both. Also widens the optional create/append param types to 'T | undefined' so they satisfy exactOptionalPropertyTypes when a key is passed explicitly.
eris-ths
added a commit
that referenced
this pull request
Jun 23, 2026
Follow-up to #444: the supersede PR updated docs/verbs.md and the ctx body sections, but three phase-1 references were left stale across the repo: - AGENT.md TOC anchor still pointed at '...alpha-phase-1' - CLAUDE.md one-liner said 'phase 1 は record のみ' - docs/playbook.md ctx-only patterns said 'ctx ships only record today' All now reflect the shipped surface (record / supersede / list / show + OKF) and the remaining phase-2 verbs (fork / chain / status).
eris-ths
added a commit
that referenced
this pull request
Jun 23, 2026
Folds three fragments into CHANGELOG.md and bumps package.json + package-lock.json (root + packages[""]) to 0.7.1: - Added: ctx supersede (#444) — the first phase-2 ctx verb. - Changed: changelog-release now bumps the manifests (#441/#442) and refuses orphan fragments (#441). Alpha patch bump (minor reserved while ctx is still phase-2). This also re-aligns the released version with main: v0.7.0 predated #443/#444, so the bin was reporting 0.7.0 while carrying unreleased verbs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The first phase-2
ctxverb:ctx supersede <old-id> --fact "...".ctx records are immutable on save, so a "correction" is not an in-place edit — it records a new fact whose
supersedesfield points back at the one it replaces. The old record is never mutated, so the ledger keeps both and the supersession is reconstructable from the forward-only link alone.Surface
ctx supersede <old-id> --fact "..." [--tag ...] [--by ...]— records the correction with asupersedeslink. A missing target is a recoverable not-found (a correction must point at something real); a self-supersession is rejected at the domain boundary.ctx list— folds superseded facts out by default (current view);--allkeeps every fact, marking the superseded ones.ctx show <old-id>— stays readable, resolves the reverse link at read time assuperseded_by, an array of successor ids (empty while current; more than one when two independent corrections fork the same fact — both are reported rather than silently picking one).Invariants
listmeans the store is genuinely empty.supersedeskey is omitted from an ordinary record's YAML, so phase-1 records round-trip byte-for-byte.Commits
feat(ctx): phase-2 supersede— domain field + use case + handlers + wiring + docs + tests.refactor(ctx): supersede follow-ups— fork-aware reverse link (returns all successors), drop an unreachable empty-message branch.refactor(ctx): dedup write path, sort comparator, tag parsing—appendFact()(shared write path),byNewestFirst()(shared comparator),parseTagList()(shared--tagsplitter).Verification
node tests/run.mjs, exit 0).--all→ show reverse-link array → fork (two successors) → error paths (missing target, malformed id, missing--fact) → byte-stable YAML.Remaining phase-2
fork/chain/status.chainis the design test the docs flag (junk-drawer risk at the 100-record scale);supersededBynotes where a persisted reverse index would land if that materializes.🤖 Generated with Claude Code