Releases: mow-coding/zettel-kasten
Release list
v0.3.170 - Runtime AI-Operator Discipline
v0.3.170 - Runtime AI-Operator Discipline
v0.3.170 turns three recurring operator-AI discipline failures into genuine,
runtime-visible norms. They generalize beyond any one AI and echo behavior WOM
holds itself to. This is a docs-only, additive release: no command, schema,
receipt, or archive change, and no new WOM-enforced check.
Prime directives (unchanged)
Guidance, not enforcement. Every norm in this release is behavior an operator
AI applies while reading its runtime surfaces. WOM does not validate provenance
fidelity, tool enumeration, or state carry-over, and this release adds no code
check that does. The claim is only that the norms are now written where an
operator AI will see them.No archive or schema change. No migration, no id rewrite, no hash change, no
new receipt, no new CLI topic. Theai-response-concept-guidetopic enum is
untouched; the references to the norms are prose only.
What ships
-
A normative
## AI-Operator Disciplinesection on the runtime-visible
surfaces. It is added to the threeAGENTS.mdtemplates
(personal/company/family, as one identical block placed after## AI Intake Protocol), the runtimeSKILL.md(placed before## Plain-Language for Humans), andwom-ai-runtime-skill-plugin-layer.md(a normative subsection plus
three matching bullets in## Skill Templateso the mirror stays honest). Three
crisp norms:- PROVENANCE FIDELITY. Record the source the human ACTUALLY encountered — the
exact video, edition, translation, or language they saw — as the provenance of
their thought. Do not silently "upgrade" it to a more authoritative or original
source. If a better source exists, ASK; if it is recorded, keep it as a SEPARATE
ref, never as a replacement of the encountered one. The zettel preserves the
user's real provenance, not the canonical work behind it. - ENUMERATE TOOLS BEFORE DECLARING IMPOSSIBLE. Before saying a task cannot be
done, or quietly degrading it ("verbatim not possible, I'll summarize"),
systematically check the installed and available tools: local CLIs, MCP servers,
and the derive-text tool-readiness surface. One or two failed probes are not
proof of impossibility. - CARRY ESTABLISHED STATE. Carry forward what is already set up or approved —
in this session or recorded inops/operational-context.yml(credentials
configured, permissions granted, resources present). Do not re-ask for or
re-confirm already-established state as if first-time. When unsure, CHECK the
recorded context (operational-context, receipts) before asking again.
- PROVENANCE FIDELITY. Record the source the human ACTUALLY encountered — the
-
A complementary source-substitution axis in
text-provenance-hierarchy.md.
A new## 7. Encountered-Source Fidelity (Source-Substitution Axis)subsection
names both axes explicitly so the provenance model is complete:- the existing derivation-tool axis (L0): do not overwrite the source object
just because a better parser, OCR model, or AI model appears later; - the new source-substitution axis: do not replace the source the human
actually encountered with a "more authoritative" or original one.
They are orthogonal and complementary — one protects the object from re-derivation
by a newer tool, the other protects it from being swapped for a different source —
and both preserve the truth of what the user's memory is based on. The subsection
cross-references the AI-Operator Discipline norm. - the existing derivation-tool axis (L0): do not overwrite the source object
-
Two one-line descriptive references (D3).
ai-response-concept-guide.md
(§8 Relation To Existing Docs) andai-response-contract.mdgain a sentence
pointing to the discipline norms, in the existing "guidance the AI applies; the
command validates nothing and enforces nothing" register. No new concept-guide
topic, no code enum change, no test-pinned substring altered.
Why these are norms, not box-ticking
The three failures came from an operator AI's own self-critique: it proposed
swapping the Korean-subtitled video the user watched for the original-language
one (corrupting the provenance of the user's thought); it declared verbatim
subtitle capture impossible after a couple of probes when an already-installed
tool could do it in one shot; and it re-asked for credentials and permission that
had already been configured and repeatedly approved. Each generalizes to any
operator AI, so they are written as genuine behavior, complementary to the
existing plain-language convention (v0.3.165) and operational-context rehydration.
Safety and scope
Docs-only. No archive migration, no id rewrite, no hash change, no new command,
no new schema, no new receipt. No claim that WOM validates or enforces any of the
norms. The concept-guide and contract commands stay read-only, write-nothing,
provider-free, and leak-guarded; their pinned Status headers and link substrings
are unchanged.
Upgrade
See UPGRADE.md. All changes are additive guidance; no migration is required.
v0.3.169 - Operator-Feedback Delivery Ledger + Batched Mark-Delivered
v0.3.169 - Operator-Feedback Delivery Ledger + Batched Mark-Delivered
v0.3.169 closes the v0.3.149 "Still Future: feedback status board" item. Operator
feedback records already carried a status field, but there was no read-only
board to aggregate delivery status and no way to commit a delivery boundary in one
action — the operator AI hand-edited each record's status through
operator-feedback-record --status, one record at a time, and lost track of "how
far did I deliver". Two commands ship together, both additive: no archive
migration, no id rewrite, no hash change.
Prime directives (unchanged)
Privacy — status and ids only. These commands aggregate and mutate delivery
STATUS metadata. They never read a feedback body and never echo feedback ref
values, title values, local absolute paths, tokens, or secrets. The record on
disk holds a feedback ref and title; both are deliberately projected out of
every ledger/mark-delivered result and receipt.Truth — metadata lifecycle only, no external submission.
deliveredhere
means "the operator marked it delivered", the same trust level as the existing
--status delivered. WOM performs no external submission and proves no human
receipt;external_submission_performedstaysfalse.
What ships
-
Read-only delivery ledger (
operator-feedback-ledger). Aliases
feedback-ledger,feedback-board. Enumeratesops/feedback/*.ymlvia
safe_archive_globand, reading ONLY status + feedback id + safe timestamps,
returns counts by status (draft/delivered/acknowledged/resolved/archived), a
pending list ofdraftfeedback ids, and the newest delivery-boundary
timestamp among delivered records. It writes nothing and requires--dry-run.
Malformed or non-mapping records are counted into anunreadablebucket and
skipped so one half-written file never fails the whole board. -
Approval-gated batched mark-delivered (
operator-feedback-mark-delivered).
Aliasfeedback-mark-delivered. In one action it marks every pendingdraft
record asdelivered, stampsdelivered_at, setsreviewed_by, and refreshes
updated_at.--dry-runpreviews which records would transition (by id and
current->new status) and writes nothing.--approve(which requires a safe
--reviewed-by) reads each record, PRESERVES every other field verbatim
(feedback ref, title, related releases, resolved_in,
body_managed_by_this_record,external_submission_performed), re-validates
the mutated record against the shipped schema, writes the transition atomically
per record, and writes a single delivery-boundary receipt under
receipts/operator-feedback/delivery-batch.<timestamp>.<batch-digest>.json
recording the ids, count, reviewer, and a per-batch content digest. The filename
carries that digest (not the whole-second timestamp alone) so two batches
committed within the same wall-clock second cannot collide and silently
overwrite each other's audit receipt.--only <id>marks a single record.Three properties hold by construction:
- Draft-only. It transitions only
draft -> delivered; acknowledged,
resolved, and archived records are never touched (byte-identical after a run). - Idempotent. Because it filters to
draftbefore transitioning, a re-run
once no drafts remain marks nothing new (delivered_count: 0, no records
rewritten). A no-op approve also writes NO receipt — the delivery-boundary
receipt is emitted only when at least one record actually transitioned, so
empty zero-delivery boundary artifacts never accumulate. - Fail-safe per record. A malformed/corrupt record in the target set is
reported and skipped; it never aborts or half-writes records already
validated.
- Draft-only. It transitions only
Why mark-delivered does NOT reuse the record writer
operator-feedback-record rebuilds the record dict entirely from CLI arguments
and never merges the on-disk record. Routing mark-delivered through it would drop
feedback_ref/title/related_releases/resolved_in (there are no flags to
pass them) and break the shipped schema's 11-field required contract. Instead
mark-delivered reads the existing YAML, mutates only the delivery fields, and
re-validates against operator-feedback.schema.json before writing. A dedicated
test asserts the mutated record still conforms to the shipped schema — the release
gates do not run schema validation, so this guarantee is pinned by pytest.
Delivery-boundary honesty
Only records stamped by mark-delivered carry delivered_at. Records that reached
delivered through the older operator-feedback-record --status delivered path
have no delivered_at, so for those the ledger's boundary falls back to their
updated_at. The ledger reports delivery_boundary_stamped_count separately and
labels the boundary as the newest available delivery timestamp, not authoritative
proof of when — or whether — anything was delivered externally.
JSON-contract and schema additions (all additive)
operator-feedback.schema.jsongains two OPTIONAL string properties,
delivered_atandacknowledged_at(not added torequired, so existing
records still validate).- New schema
wom-kit/schemas/operator-feedback-delivery-receipt.schema.json
(wom-kit/operator-feedback-delivery-receipt/v0.1) for the batch receipt, with
external_submission_performed: const falseanddelivery_is_metadata_only: const true.
Test honesty note
New tests use temp-dir fixtures copied from the fake-life archive and never touch a
real archive. They pin: the ledger aggregates counts + a pending list and does NOT
echo a seeded secret-ish feedback ref OR title; mark-delivered --dry-run previews
and writes nothing; --approve transitions only draft->delivered, stamps
delivered_at + reviewed_by, writes exactly one receipt, is idempotent (second
run no-ops AND writes no new receipt), and requires --reviewed-by; --only marks
a single record; seeded acknowledged and resolved records are byte-identical after a
run; the mutated record still conforms to the shipped schema; a malformed record is
reported as unreadable by the ledger and skipped by mark-delivered while the valid
draft still transitions and the return code stays sane; two batches committed in the
same wall-clock second keep two distinct receipts (per-batch digest in the filename);
a hand-authored record whose internal feedback_id is a secret-ish URL/token has that
id redacted to the safe file stem in the ledger pending list, the mark-delivered
output, and the receipt; and neither the mark-delivered result nor the on-disk batch
receipt contains the secret-ish ref or title.
Safety and scope
No archive migration. No id rewrite. No hash change. The ledger is read-only;
mark-delivered writes only status-metadata transitions and a receipt after
approval. delivered is a metadata stamp, never a claim of external submission.
All new commands are additive.
Upgrade
See UPGRADE.md. All changes are additive; no migration is required.
v0.3.168 - Draft-Time Identity Hygiene + Honest Human Affirmation + Continuation Edges
v0.3.168 - Draft-Time Identity Hygiene + Honest Human Affirmation + Continuation Edges
v0.3.168 sharpens three lifecycle safety edges without loosening any of them:
new draft ids stop carrying a misleading _draft slug, the mint human-review
checklist can finally be satisfied by an attributed, auditable CLI act instead
of a raw YAML hand-edit, and a continues edge type gives same-thread follow-on a
first-class home. Five changes ship together. Every one is additive: no archive
migration, no id rewrite, no hash change, all flags opt-in.
Prime directives (unchanged)
Identity safety — forward-only. No existing canonical id is ever renamed or
normalized; mint gains no id-rewrite path. Only the generator of new draft ids
changes, before any reference can exist.Lifecycle safety — no silent auto-delete. Mint never deletes the consumed
inbox draft. Retirement stays its own approval-gated step.Lifecycle safety — no AI self-affirm. A human-review checklist item can only
be affirmed with an attributed--reviewed-by;--affirmis inert without one,
cannot override machine-enforced items, and is recorded auditably in the receipt.
What ships
-
Draft-id hygiene, forward-only (Item 마).
make_zettel_idbuilds a new draft
id aszet_<day>_<time>_<slug>, whereslugis the title reduced to
[a-z0-9_]. A titleless title, or a pure-Hangul title (no ASCII alphanumerics),
reduces to an empty slug and hit theor "draft"fallback — producing a
misleadingzet_<ts>_draftid. The fallback token now becomesnote. This is
the ONLY change: the timestamp segments and thezet_..._slugshape are
untouched, and the fix runs once at draft creation, before any reference target
exists (canonical id/filename, edges,source_refs, object manifests, the
mint receipt path, the draft snapshot path all derive from the id only later, at
mint). No existing id is renamed; existingzet_<ts>_draftids (and real title
slugs that happen to contain the word "draft") are untouched. The same-second
collision loop is unchanged, so two titleless drafts become…_noteand
…_note_2, two distinct inbox files. -
Attributed mint affirmation (Item 나). The two
needs_human_reviewchecklist
items (one_clear_purpose,sensitive_content_reviewed) arerequired: true
and, until now, could only be satisfied by hand-editing the draft's
mint.checklistYAML.mint-zetgains a repeatable--affirm <item_id>flag
(argparseaction="append", matching every other multi-value flag) plus a
required--reviewed-by. Affirmed ids are threaded through the whole dry-run
chain intobuild_lifecycle_checklist, where — only when the operator did not
hand-edit the YAML (explicit is None) and the id is one of the two human items
— the item is marked passed with a distinct source labelcli_affirmation
(separate frommint_frontmatter,legacy_promotion_frontmatter,machine). The
mint receipt gains an attributedaffirmationsblock:{item_id, affirmed_by, affirmed_at}for each applied affirmation. Three mechanical gates hold:- Inert without an attributed reviewer.
--affirmwith no--reviewed-by
is a hard error (--affirm requires --reviewed-by …), so no affirmation is
ever recorded unattributed. - Scoped. The service accepts affirmed ids only for the two human-review
items; any other id is rejected (--affirm only accepts human-review items: one_clear_purpose, sensitive_content_reviewed.). - Cannot override machine items.
object_id_only/allowed_edgesstay
machine-blocked: they are excluded from the affirmable set AND the
machine-enforced-blocked branch precedes the affirmation branch, so a machine
block is unreachable-to-flip.
A YAML hand-edit still wins first, so--affirmnever flips an explicit YAML
false.
Honest residual. Like the pre-existing
--reviewed-bygate,--affirmcannot
cryptographically prove the reviewer string names a real human. It introduces no
new self-affirm hole beyond the existing--reviewed-bytrust boundary; it only
guarantees (a) no affirmation is recorded without an attributed reviewer and (b)
the affirmation is auditable in the receipt. We deliberately do NOT add an
ai_runtime:/ai:-prefix rejection: it would be trivially bypassed
(--reviewed-by person:not-really), giving false assurance, and would break the
legitimate audited case where an operator's id carries a tool tag. Auditability,
not string-sniffing, is the honest guarantee. - Inert without an attributed reviewer.
-
No silent auto-delete; discoverability pointer only (Item 다). Mint keeps
preserves_draft_reference: trueand never deletes the consumed inbox draft. A
successful mint result now carries anext_safe_actionslist with a single human
sentence pointing toarchive retire-draft --zettel-id <id> --dry-run, printed in
text mode as aNext:line. There is no opt-in chain flag in this release; any
future chain must run retire in--dry-runonly or demand its own--approve+
--reviewed-by, never deleting the draft implicitly. The doctor's existing INFO
prose (minted_inbox_draft_twin_pending_retire) is left unchanged —info()/
warn()cannot carry a concretesuggested_command, and extending them is
out-of-scope cross-cutting work; the mint-time pointer routes the operator at the
exact moment the stale draft is created. -
Base
continuesedge type (Item 라). Acontinueslink type is added to the
base vocabulary in bothtypes.ymlfiles (the KIT base and the fake-archive test
fixture). Semantics are carved away from every overlapping neighbor:continues
means a same-thread continuation / next installment in the SAME line of thought
or work — NOTderived_from(source/provenance), NOTreferences(topical
citation), NOTderived(a distinct later product built from this zet), NOT
supersedes(replacing an older version), and NOT a generic ordered process step
(sequence). It is deliberately NOT added to
CONNECTION_IMPORT_RECOMMENDED_EDGE_TYPESandCONNECTION_EDGE_RELATIONSHIP_VOCABULARY
is left untouched, because migration/revert only ever touch the recommended set —
so a base-only member keeps every pinned migration and connection-vocabulary test
green. Acknowledged limitation: archives that vendored their owntypes.yml
will not receivecontinuesviamigrate link-types-v0.3; they add the entry
manually (it is additive). -
Draft-time
--kindvalidation +--list-kinds(Item 가).create-draft
validated nothing about--kindbefore writing it to frontmatter. It now resolves
the archive's ownnote_kind_rules(load_zettel_rules(root))and, if the kind is
unknown, appends a WARNING (not a blocker) that names the kind and enumerates
the valid kind ids — consistent with mint, which only warns on unknown kinds. The
default staysfleeting_capture, and no argparsechoices=is added (valid kinds
are archive-owned and may be custom;choices=is fixed before the archive root
is known and would be stricter than mint). The warning is now printed in the
non-dry-run text path too. A read-only--list-kindsflag lists the archive's
valid note kinds and exits without writing (no--title/--bodyrequired).
JSON-contract additions (all additive)
- Mint receipt: a new
affirmationsarray (item_id,affirmed_by,affirmed_at),
empty when no affirmation was applied.mint-receipt.schema.jsonuses no
additionalProperties: false, so the field is additive and non-breaking. - Mint result: a new
next_safe_actionsstring list.
Test honesty note
New tests use temp-dir fixtures copied from the fake-life archive and never touch a
real archive. They pin: unknown-kind warns-not-blocks and lists valid kinds;
--list-kinds writes nothing; affirm mints without a YAML edit; affirm inert
without a reviewer; the receipt affirmations attribution and the cli_affirmation
source label; affirm cannot override object_id_only; the next_safe_actions
retire pointer; a continues edge is writable AND migrate link-types-v0.3
dry-run/approve/revert stay green; and a titleless / pure-Hangul draft gets a
_note id with two same-second drafts distinct, while an existing zet_<ts>_draft
fixture id is untouched.
Safety and scope
No archive migration. No id rewrite — existing canonical ids are unchanged. No hash
change. --affirm's honest residual is stated above: it guarantees attribution and
auditability, not proof of humanity. continues is base-only and reaches vendored
types.yml archives only by a manual, additive edit. All new flags are opt-in.
Upgrade
See UPGRADE.md. All changes are additive; no migration is required.
v0.3.167 - Snapshot-Drift-Aware Reconcile + Retire-Draft Reconcile
v0.3.167 - Snapshot-Drift-Aware Reconcile + Retire-Draft Reconcile
v0.3.167 extends the honest mint-receipt reconcile family without ever loosening
its prime directive: a real content change must NEVER be misclassified as
format drift. Classification only decorates the human decision; every reconcile
still shows on-disk content and stays approval-gated, and every uncertain case
falls back to the stricter content_change (which requires an explicit
--content-changed-ack). Five changes ship together.
The prime directive (unchanged)
Classification only decorates. When normalized-content comparison is at all
uncertain, classifycontent_changeand require acknowledgment. A snapshot that
is content-tampered NEVER anchorsformat_drift.
What ships
-
Snapshot-drift-aware
format_drift(Item 1).remint-reconcilenow grants
format_drifteven when the draft snapshot itself has drifted — but ONLY behind
a two-independent-proofs rule sourced from un-tampered inputs. A new anchor tier,
normalized_content_match(Tier B), sits between the existing raw-sha
clean_anchor(Tier A) and thecontent_changefallback. Tier B is granted only
when: (1) the current canonical body is byte-identical to the snapshot body under
the one normalized-equality definition (CRLF/CR→LF, strip one leading BOM, zero
Unicode normalization, no space collapsing); (2) the snapshot's own raw-vs-
normalized delta is provably newline/BOM-only; and (3) the frontmatter field-diff
is empty. Because the drifted snapshot is only newline/BOM-anchored (not
sha-anchored), that field-diff is the union of two independent checks, both of
which must be empty: a full-field reconstruction comparing every content
frontmatter field of the current canonical against the snapshot (visibility,
kind,facets,provenance,edges,created_at,source_refs, …, not just
id/title), and a cross-check against the mint receipt's recordedzettel
(id/title). This closes the tampered-snapshot hole in full: a canonical edit
to ANY content field falls tocontent_change, and a snapshot whose frontmatter
was tampered to match a tampered canonical (which would make the full-field diff
reproduce itself and read empty) is still caught by the receipt cross-check. A new
classification_basisfield (clean_anchor/normalized_content_match/
content_change_fallback) records why aformat_driftwas granted. -
retire-draft-reconcilesibling command (Item 2). A new CLI-only command
honestly reconciles a retire-draft receipt with its four refs
(source / target / mint_receipt / snapshot) after newline/BOM or content drift.
It reuses the same honesty primitives and inherits the Item 1 discipline: a
target/snapshot ref isformat_driftonly when the shared mint-reconcile
classifier proves the canonical and snapshot content-identical AND the structural
newline/BOM delta guard holds; themint_receiptpointer ref has no format
dimension and iscontent_changeon any mismatch; a removedsourcedraft is
not a drift. The doctor now attaches asuggested_commandroute to the
mint_retired_draft_sha_mismatchfinding (previously a bare error with no
discoverability), mirroring the mint route. New sibling audit receipts live under
receipts/mint/retired-draft-reconciles/. -
--strip-bom(Item 3). An opt-in boolean on bothremint-reconcileand
retire-draft-reconcilethat removes exactly the 3-byte leading UTF-8 BOM.
Stripping a leading BOM isformat_driftby definition (text content unchanged).
Guards: a no-op refusal when there is no leading BOM (nothing is rewritten); a
hard content-preserving invariant asserted before an atomic rewrite; and — the
load-bearing one — a BOM strip NEVER bypasses the content-change ack gate. When a
file also carries a real content edit,--strip-bomstill requires
--content-changed-ack. -
live_execution_allowed_nowhonesty (Item 4). The object-storage upload
run-outcome payload previously reportedlive_execution_allowed_now: falseeven
on a genuinely executed upload — a self-contradiction with
execution_status: executed. The RUN-outcome field now truthfully reports what
the run did (trueonly on a real executed upload;falseon preview/blocked).
The static contract-preview capability fields elsewhere are a different signal
and remain unchanged. -
--multipart-thresholdoverride (Item 5). A clearly-labeled
validation/testing aid onobject-storage-upload. The 5 GiB default is unchanged;
an override is code-bounded to[64 MiB, 5 GiB](below the part-size floor or
above the default ceiling is refused with a blocker, so a forced multipart still
exercises the real part-splitting path and cannot fragment a tiny object). The
effective threshold andpart_countare now recorded in the durable upload
receipt. The override affects only the recorded/used threshold, never the local
sha256 == object_idcheck nor the provider-HEAD-after gate.
JSON-contract additions (all additive)
mint-reconcile-receipt.schema.json:classification_basis,bom_stripped.mint-retired-draft-receipt.schema.json: areconcileprovenance block.- new
retire-draft-reconcile-receipt.schema.json. object-storage-upload-receipt.schema.json:effective_multipart_threshold_bytes,
part_count.
The drift_class enum stays exactly ["format_drift", "content_change"]. No schema
uses additionalProperties: false, so every field is additive and non-breaking.
Test honesty note
test_remint_reconcile_drifted_snapshot_falls_back_to_content_change was revised,
not merely kept: its missing / crlf_plus_content / bom_plus_content
subscenarios still classify content_change (a real HUMAN EDIT or no anchor), and
new pure-format subcases were added as a sibling test that flip to format_drift
under Tier B. The three v0.3.162 integrity fences (a content-plus-newline drift is
never softened to format; a title edit is content_change; a non-allowlist
frontmatter edit is content_change) stay green untouched.
Safety and scope
No archive migration and no hash change. New flags and the new command are opt-in.
The reconcile family remains CLI-only (no MCP surface). When in doubt, the
classifier chooses content_change.
Upgrade
See UPGRADE.md. All changes are additive; no migration is required.
v0.3.166 - Selectable Upload Key Strategy + Safe Adopt-Existing
v0.3.166 - Selectable Upload Key Strategy + Safe Adopt-Existing
v0.3.166 fixes the highest-consequence gap in the live object-storage upload
adapter (WOM #11): an operator whose objects already live under their own key
layout could have those objects re-uploaded, and — worse — a recorded key could
be trusted for a skip against a location the object was never at. This release
makes the upload key selectable and recorded, adds a safe adopt-existing
workflow, and pins one non-negotiable invariant: a skip is legal only when a live
HEAD proves the object is present at the recorded key with a matching size, in the
same run that skips. When in doubt, upload.
The one invariant
A skip is legal only when backed by a live HEAD proving present-at-the-recorded-
key + size-match, right now, in the same run that skips. Never skip on a purely
computed or purely claimed key.
Concretely, under a live transport the executor ALWAYS re-HEADs the recorded
remote_key before skipping. A recorded key that 404s re-uploads (never a silent
skip — silent data loss on restore is the cardinal sin). The re-HEAD matches the
recorded verification: a location adopted presence+size is re-checked presence-only
(no whole-object download just to confirm a skip), while a content-hashed upload
keeps its stronger checksum re-check. The resume ledger's terminal-success
short-circuit is subordinate to this live proof — once a re-HEAD proves an object
absent, the re-upload is forced past any stale ledger row, so a wiped remote is
never silently skipped from a prior run's ledger. Plan and apply resolve the same
key by construction: the plan echoes the fully-resolved remote_key into each row,
and apply refuses the run (fail closed) if its re-resolved key diverges. The plan
verdict is strategy-aware — a prior location under a different key layout does not
predict a skip the apply path would then re-upload.
What ships
-
Selectable key strategy.
--key-strategy {sha256_content_addressed, prefix}
(default unchanged, byte-identical to before), plus--key-prefix <literal>and
--key-append-extension, onobject-storage-upload,
object-storage-upload-plan,object-storage-upload-verify, and the new
object-storage-adopt-existing. Theprefixstrategy places an object at
<configured-prefix>/<sha256>[.<ext>]; the default lands at exactly
sha256/<first2>/<sha256>with no prefix prepended. -
Two-field key model (additive, no migration). Every object-storage location
and execution receipt now records a newremote_key(the literal
bucket-relative key the object is/was PUT/HEAD at) alongside the unchanged
content-addressedkey_hint. The idempotency HEAD, the skip matcher, and future
download tooling key offremote_key; the digest audits keep validating
key_hint, so no existing location is flagged corrupt and the default strategy's
remote_keyequals itskey_hint. -
object-storage-adopt-existing(the 158 GB false-skip fix). A verified
adopt (--approve+ a live transport) HEADs each computed client key and adopts
ONLY on presence + Content-Length size-match — not a content hash, because a full
re-hash would GetObject the whole archive (R2 has no server-side whole-object
sha256).--content-hash-verifyis an explicit per-object opt-in. A 404 /
size-mismatch is not adopted, so a wrong prefix or extension self-limits to zero
adopts and those objects simply re-upload. A declared adopt
(--accept-unverified-adopt, a flag distinct from--approve) records a
NON-gatingdeclared_uploadedlocation that never skips a PUT. Adopt reports
verified-count vs total so a template miss is visible, never a silent partial.
Because a verified adopt HEADs presence-only, adopting a 158 GB set costs a HEAD
per object, not a download per object. Verified adopt is a live surface, so it
honours the same tiny-first tiered gate asobject-storage-upload: a bulk
first-live adopt refuses until a single tiny-first object (--only <id>) has
proved the store. -
Audits accept the new strategy without weakening. The three manifest audits
and the execution-receipt doctor audit accept a correctprefix-strategy
location/receipt AND additionally verify that a non-defaultremote_keybinds
the record's digest — catching a valid-looking key for the wrong object. The
upload-evidence writer now shares the single content-addressedkey_hint
producer (the duplicate literal is gone). The execution-contract preview tells
the truth for both strategies. -
remote_keyhas its own validator. It holds a path within the bucket
(slashes, dots, and the archive-id colon are legal) but never a leading slash,
.., a bucket name, an endpoint host, or a URL — leak-checked so public-privacy
stays green.
Discoverability
object-storage-upload-plan now emits a visible hint when objects may already
exist under a different key layout ("run object-storage-adopt-existing with your
--key-prefix before --approve to avoid re-uploading"), and a default-strategy
upload warns when the store already has non-default-strategy locations.
Safety and scope
No archive migration and no hash change. The default strategy is byte-identical to
v0.3.165. This release builds the strategy/adopt machinery so it is correct
whenever the live transport is enabled; it does not change live-execution gating.
Verified adopt is presence+size only and is labeled as such
(remote_key_verification: presence_size); a declared adopt is labeled "claimed,
not verified — will NOT skip a PUT".
Upgrade
See UPGRADE.md. New flags are opt-in; existing runs behave exactly as before.
v0.3.165 - Plain-Language Guidance Convention
v0.3.165 - Plain-Language Guidance Convention
v0.3.165 makes "explain it in everyday language" a normative convention for WOM
operator AIs, not a one-off reminder. WOM already had an
ai-response-concept-guide that translates WOM CONCEPT words for a human, but
nothing told an operator AI to translate git/infrastructure jargon ("fetched to
the mirror", "checkout", "pin", "manifest") when it reports version or
infrastructure state to a person. This release adds that convention to the
runtime-visible surfaces so every WOM operator AI applies it on the first try,
and adds a git/infra lookup layer to the concept guide so the plain phrasing is
one command away.
What ships
-
A normative "Plain-Language for Humans" convention on five runtime surfaces.
The personal/company/familyAGENTS.mdtemplates, the runtime
templates/ai-runtime/wom-archive/SKILL.md, and the normative
wom-kit/docs/wom-ai-runtime-skill-plugin-layer.mddoc (plus one bullet in its
Skill Template list) now carry the same short rule: when an AI operator
addresses a HUMAN, it must translate git/infrastructure/WOM-internal jargon
into everyday language, keeping the exact technical term in parentheses or in
the logs only. Each surface keeps its own terse style. The canonical worked
examples are shared across all five:the update files arrived but the update button hasn't been pressed yet (fetched, not checked out) a saved bookmark to a specific version (a pin) the list of which files exist and their fingerprints (the manifest) -
A git/infra terminology translation layer in
ai-response-concept-guide.
A newgit_infra_termstopic and section emit ko-KR/en-US everyday phrasings
forfetch,checkout,pin,manifest,hash,commit,tag,branch,
HEAD,remote,mirror,clone,diff,staged,rebase, andstash.
This is complementary to the existing WOM operational-term layer (edge types,
lifecycle states, connection kinds): it covers git/infra words, not WOM concept
words, so the two layers do not duplicate each other. The guide gains the
capability flaggit_infra_term_translation_available, the guide-contract flag
translate_git_infra_jargon_for_humans, and a safe-routing lookup entry.
Look it up with:archive ai-response-concept-guide <archive-root> --topic git_infra_terms --locale en-US --dry-run --format json
The boundary (what this is NOT)
- This governs human-facing prose only. Machine, JSON, and receipt output
stays exact and unchanged. - The convention is guidance an operator AI applies, enforced by the AI
reading it — not a code check. WOM does not validate or enforce plain-language
output, and nothing in this release claims that it does. - The
ai-response-concept-guidecommand still writes nothing, calls no
providers, reads no source bytes, and echoes no local absolute paths, provider
URLs, or secret values.
Upgrade
No archive migration is required. No hash change. The additions are guidance
prose and read-only concept-guide surfaces. See UPGRADE.md.
v0.3.164 - Object-Storage Upload Adapter (Stage 2): Real SigV4 R2/S3 Transport
v0.3.164 - Object-Storage Upload Adapter (Stage 2): Real SigV4 R2/S3 Transport
v0.3.164 lands Stage 2 of the WOM #11 live object-storage upload adapter: a real,
hand-rolled AWS SigV4 R2/S3-compatible upload transport. WOM is now
network-CAPABLE for an approved object-storage upload. Capable is not automatic:
a live --approve upload still fails closed without env-only credentials, a safe
--reviewed-by, a resolvable endpoint/bucket, and a met tiered tiny-first gate.
This release ships with unproven_against_live_provider: true on the release note
and every execution receipt, and stays that way until the tiny-first human runbook
below confirms the first live object.
What ships
- Real SigV4 transport (
S3CompatibleTransport). PUT and UploadPart sign the
real lowercase-hex payload sha256; HEAD/GET signUNSIGNED-PAYLOAD. The
canonical URI/query encoding is RFC-3986 correct (unreservedA-Za-z0-9-._~,
everything else uppercase%XX, content-addressedsha256/<first2>/<hex>
key separators preserved, path encoded once). Header canonicalization,
SignedHeaders, the 4-line string-to-sign, and the 4-stage signing key
(kDate→kRegion→kService→kSigning, raw-byte intermediates) are all
byte-exact. Region isautofor Cloudflare R2 (threaded into both the
credential scope andkRegion);generic-s3requires an explicit region.
The SigV4 core is pinned against the published AWS documented example — the
canonical request, string-to-sign, and final signature all match the third-party
values. - Whole-object verification by re-download-and-hash (the showstopper this stage
resolves). The executor compares a lowercase-hex sha256 at HEAD-after. Rather
than depend on a provider-stored SHA-256 — which is not reliably available: R2
does not implement GetObjectAttributes, marks thex-amz-checksum-*headers
"Feature Not Implemented", and a SHA-256 multipart checksum can only be COMPOSITE
(never the whole-object hash) — the transport verifies byHeadObject(presence- size) followed by
GetObjectand re-hashing the returned bytes to hex. Uploads
still sign the real payload SHA-256 in SigV4 (x-amz-content-sha256) for on-wire
integrity, but no provider checksum surface is trusted. The shipped executor
comparison is unchanged and now passes for a genuine upload.
- size) followed by
- Multipart. Objects at or above the 5 GiB threshold use a plain multipart
upload (create → part PUTs → complete). Nox-amz-checksum-type: FULL_OBJECTis
sent — that combination is unsupported for SHA-256 on both AWS S3 and R2. The
CompleteMultipartUpload request carries only the<Part>list and an explicit
text/xmlContent-Type (AWS rejectsapplication/x-www-form-urlencodedfor that
call). Whole-object integrity is verified by the same HEAD+GET-rehash path. If the
re-download does not hash to the content id, the object FAILS — it never passes on
ETag or size, and the completed-but-wrong object is deleted so no orphan accrues
storage cost. Arate_limitedfailure mid-multipart is retried by the bounded
loop to the attempt ceiling, exactly like the single-PUT path. - Bounded retry + cost ceilings. A per-object retry loop backs off exponentially
with jitter up to a hard attempt ceiling onrate_limited(429/503/SlowDown/
InternalError/conn-reset), then fails closed asfailed_rate_limited. Anauth
status (403/400/SignatureDoesNotMatch/InvalidAccessKeyId/RequestTimeTooSkewed)
fails closed immediately with ZERO retries — retrying a bad signature burns
Class-A ops and can never succeed. A HARD cumulative provider-PUT ceiling
(OBJECT_STORAGE_TOTAL_PUT_CEILING) bounds cost across the whole run,
independent of--max-objects.retry_summary.backoff_ms_totalis now a real
accumulated value. - Tiered tiny-first gate. A run may not exceed the live-acceptance tier the
store's prior durable execution receipts have proved. A bulk first-live run
REFUSES withtiered_gate_unmetuntil the single small object is proved. Tier
advancement is derived from durable receipt facts —result_status,
bytes_uploaded, and the count of distinct successful one-per-object receipts —
not a bare flag the caller sets. A batch (tier 3) requires a proven large-object
/ multipart upload (tier 2) plus enough distinct landed objects. Each live tier
stays a human--approvestep. - Single networking seam. Every transport method builds a fully-signed request
and hands it to an injectedsend. The only place stdlib networking is reachable
is one_default_urllib_sender()factory; the CLI wires it only for a real
--approverun. No dependency was added (still PyYAML only). - Secret discipline extended. The direct-value containment guard now also covers
the derived SigV4 signing key, as belt-and-suspenders over the primary structural
guarantee that signing material lives in transport locals only. No request
headers,Authorization,StringToSign,CanonicalRequest, or provider error
body is ever recorded.
Capable != automatic
live_object_upload_adapter_implemented and provider_api_call_implemented are
now true — the live adapter really ships. But archive object-storage-upload --approve still fails closed unless every gate is met: env-only credential refs,
a safe --reviewed-by, a resolvable non-secret endpoint/bucket, and a met tiered
gate. Read-only object-storage-upload-plan/-verify and the read-only MCP tools
are unaffected.
Residual (only a real endpoint proves these)
Carried as unproven_against_live_provider: true until the first live object:
signature ACCEPTANCE by R2's authorizer; real read-after-write consistency of the
HEAD+GET verification path; real 429/SlowDown timing; the ±15-minute clock-skew
window; and real Class-A/B billing (note the HEAD+GET verification adds one
GetObject read per uploaded object). Whole-object integrity itself no longer
depends on any provider checksum surface, so it is not a live unknown.
Human tiny-first runbook (tier 1, first live object)
Upload exactly one small object, verify it end-to-end by hand, then advance tiers.
WOM_R2_ACCESS_KEY_ID=<id> WOM_R2_SECRET_ACCESS_KEY=<secret> \
archive object-storage-upload <archive-root> \
--provider-kind cloudflare-r2 \
--store-ref storage:account:<label> \
--endpoint-host <account>.r2.cloudflarestorage.com \
--bucket <bucket-name> \
--access-key-id-ref env:WOM_R2_ACCESS_KEY_ID \
--secret-access-key-ref env:WOM_R2_SECRET_ACCESS_KEY \
--only sha256:<one-small-object-hex> \
--max-objects 1 \
--skip-uploaded \
--reviewed-by kim \
--approve
After it succeeds, verify by hand: the execution receipt under
receipts/providers/object-storage-executions/, the manifest transition to a
wom_uploaded location, and the remote after-HEAD. Then advance the tiers, each a
separate human --approve run:
- T1 one small object (above).
- T2 one object at or above the multipart threshold.
- T3 a small batch (~10) with a deliberate mid-batch abort proving the
manifest lock + resume ledger. - T4 a
--skip-uploadedre-run proving nodeclared_uploadedfalse-skip. - T5 the full set.
Only after the first live object is confirmed does the doc caveat flip to
proven_against_live_provider: cloudflare-r2, one object.
What is deliberately NOT in this release
No live object has been uploaded from this release — the tiny-first runbook is a
human step. Real signature acceptance, checksum surfacing, multipart, throttle
timing, and cost are validated live, tier by tier, behind explicit human approval.
v0.3.163 - Object-Storage Upload Adapter (Stage 1)
v0.3.163 - Object-Storage Upload Adapter (Stage 1)
v0.3.163 lands Stage 1 of the WOM #11 live object-storage upload adapter as an
approval-gated WOM-kit CLI surface (not a provider MCP the AI drives). Stage 1
ships everything provable in a temp dir — the upload plan, digest-aware
idempotency, local RAW-byte verification, the execution-receipt and resume-ledger
shapes, the manifest wom_uploaded transition, and the load-bearing secret and
manifest-safety controls — while shipping NO live transport. The wire step
itself is a later, human-gated stage.
The no-network boundary (the load-bearing property)
This release is structurally incapable of a real provider call, and a reviewer
can confirm that in seconds:
- No transport that performs a socket operation ships. The upload spine calls a
provider only through an injectedObjectStorageTransport. Stage 1 ships the
abstract interface plus aNullTransportwhose every method raises, and no
class that opens a socket. Noboto3/httpx/requestsimport sits on the
upload path, andwom-kit/pyproject.tomlgains no dependency (still PyYAML). object_storage_resolve_transport(provider_kind)returns null for every
provider. There is no env var, flag, or branch in this release that resolves a
live client.archive object-storage-upload --approvefails closed with
live_transport_not_implementedbefore any credential read and before any
byte read.live_object_upload_adapter_implementedandprovider_api_call_implemented
are both false in command output and the capability matrix.
The guarantee is compositional: even if a reviewer distrusts the fail-closed
blocker, there is literally no client object to call. Making a real PUT requires
a Stage-2 code change that adds an import and rewires the resolver — a diff that
cannot land silently.
Command surface
archive object-storage-upload-plan <archive-root> --provider-kind <kind> --store-ref <label> --access-key-id-ref env:<NAME> --secret-access-key-ref env:<NAME> [--only sha256:<hex>] [--max-objects N] [--skip-uploaded] --dry-run— read-only, writes nothing,
reads no secret. Emits a per-object plan (object_id,size_bytes, the
content-addressed key shapesha256/<first2>/<64hex>, and a digest-aware
would_upload/already_uploadedverdict) and REFUSES at the service layer if
the resolved plan exceeds--max-objects.archive object-storage-upload-verify <archive-root> --dry-run— hashes each
planned object's local RAW bytes with sha256 and asserts equality with the
object id. It never uses the BOM/newline normalizer; upload verification is
byte-exact.archive object-storage-upload <archive-root> ... (--dry-run | --approve)—
the mutating command.--dry-runpreviews the plan and the execution-receipt
shape with no provider call, no byte read, and no secret read.--approve
fails closed in Stage 1. The three-way gate (reject both modes, reject neither,
reject--approvewithout a safe--reviewed-by) is enforced at the CLI and
re-enforced in the service layer.
Each command keeps the established object-storage-* family naming with the
objet-* aliases.
Idempotency and verification (digest-aware)
--skip-uploaded treats an object as already_uploaded only when a
provider-confirmed wom_uploaded manifest location exists whose key_hint
digest equals the object id — never on an external declared_uploaded evidence
record and never on a manifest-only hit without a remote HEAD. Layer A gates
cost; only a remote HEAD gates correctness. The audit and doctor enforce the
same digest invariant on every object_storage location, so even a hand-edited
manifest is caught. When a transport is injected (tests only), the per-object
spine runs a remote HEAD before upload, a single PUT below the 5 GiB multipart
threshold or a multipart upload above it (full-object sha256 verified — ETag is
never treated as the content hash), a remote HEAD after upload, and a crash-safe
append-only resume ledger that is authoritative for "this object's PUT already
succeeded."
Reliability: manifest-write hardening
The shared object-storage manifest writer now holds the same manifest lock the
objet-capture append path uses and writes via a temp+fsync+os.replace atomic
writer. Previously it ended in a bare write_text, so a crash mid-write or a
concurrent capture could corrupt the whole manifest. This fix is additive and
also hardens the existing external-upload-evidence command.
Secret and leak discipline
Both resolved key values are read only under --approve after every gate, held
in locals, and cleared in a finally block. Before any write, and on every exit
path, the fully-serialized output (receipt + ledger delta + manifest delta +
return payload) is checked by a direct-value containment guard against both key
values — the sound control that catches a bare 64-hex/40-char secret the named
regex scanners miss — backed by those scanners plus the forbidden-location
scanner as a backstop. Execution receipts and ledger rows are built from a fixed
scalar allowlist; no request headers, Authorization, StringToSign,
CanonicalRequest, or provider error body is ever recorded.
Also in this release
- New doctor check
_check_object_storage_execution_receiptsvalidating
object-storage-upload-receipt.schema.json,dry_run:false+ non-empty
reviewed_byon applied receipts, self-referentialreceipt_path, and the
digest invariant. - Read-only MCP tools
object_storage_upload_planand
object_storage_upload_verify. No upload write tool is exposed.
What is deliberately NOT in this release
The live PUT, real provider HEAD/checksum behavior, SigV4 signing, and cost
accounting. Those are a later, tiny-first, human-gated stage. This release
cannot upload to a provider.
v0.3.162 - Honest Mint-Receipt Reconcile
v0.3.162 - Honest Mint-Receipt Reconcile
v0.3.162 adds archive remint-reconcile, an honest way to re-issue a mint
receipt's recorded sha256 values after a canonical zet drifts on disk — for
example a CRLF/BOM re-checkout under core.autocrlf, or a human content edit —
without ever masking corruption or waiving human review. It also lands additive
BOM/newline parse tolerance and a doctor/retire route to the new command.
Governing Doctrine (v0.3.162 decision log)
Reconcile NEVER masks corruption, and classification NEVER waives human review.
The draft snapshot only anchors the raw draft bytes (body + draft frontmatter),
while the client's real mutation (a title correction) lives in the
mint-transformed canonical frontmatter. A body-only comparison is therefore
blind to frontmatter edits, so:
- Every
--approveshows the current on-disk content and requires
--reviewed-by. No computed class unlocks a lower-friction, human-skipped
path. - Classification only DECORATES the human decision (it names which fields
changed). It never REPLACES it. - The default class is the stricter
content_change.format_driftis granted
ONLY on positive, byte-level, independently re-derivable proof of
content-identity. Any doubt falls tocontent_change. - Hard refusals run BEFORE classification. A state that cannot be honestly
reconciled is REFUSED; it is never "fixed."
What Changed
- New command
archive remint-reconcile <archive-root> (--zettel-id <id> | --path <rel>) [--dry-run | --approve] [--reviewed-by <actor>] [--content-changed-ack] [--format text|json].--dry-run(default)
classifies and previews with zero writes;--approvere-issues after review. format_driftis proven positively: the current canonical body must be
identical to a CLEAN draft snapshot (raw sha matches the recorded sha) AND the
FULL content frontmatter — every draft key re-derived from the snapshot, with
source_refstransformed exactly as mint does — must match the current
canonical field-by-field. Only the mint-injected keys (status,updated_at,
mint{},promotion{}) are excluded from the diff, andstatusis separately
required to equalcanonical. Because the comparison spans every content field
rather than a hand-picked subset, an edit to ANY field (title, id, kind,
visibility, facets, provenance, edges, created_at, …) is caught: a
canonical-only title edit leaves the body identical but the title differs, so it
is correctlycontent_change, and the same holds for every sibling field — no
content field is invisible to the classifier.- Snapshot drift is a first-class fail-safe: a missing, BOM'd, or
otherwise-mutated snapshot is never treated as a clean anchor and classification
falls back tocontent_change. - Approval writes BOTH: an in-place mint-receipt update (recomputed shas plus an
append-onlyreconcile.historyblock with anormalized_content_digest) AND a
separate immutable audit receipt underreceipts/mint/reconciles/. Repeats get
a monotonic suffix. Both writes are atomic (temp file + os.replace). archive doctorroutes canonical byte drift: a previously-reconciled receipt
that re-drifted by newline/BOM only emits the new
mint_receipt_target_byte_drift_suspected_formatERROR; an un-reconciled sha
mismatch keeps the plainmint_receipt_sha_mismatchERROR plus a suggested
remint-reconcile --dry-runcommand. Both stay non-clean (faildoctorand
--strict). The edge-receipt evolution path is unchanged. A UTF-8 BOM on a
canonical zet surfaces a newzettel_has_bomWARN advisory.archive retire-draftnow surfaces aremint-reconcile --dry-runnext-safe
action when — and only when — retirement is blocked by the mint-target
sha-mismatch blocker. No retire gate was relaxed.- Additive parse tolerance: frontmatter parsing and receipt/JSON reads tolerate
a single leading UTF-8 BOM (utf-8-sig/ one-BOM strip). sha256 hashing still
reads raw bytes, so BOM and newline drift stay visible as a sha mismatch. New
mints pin the canonical write to LF newlines to prevent immediate re-drift. - Schemas: added
wom-kit/schemas/mint-reconcile-receipt.schema.jsonand a
reconcileobject property onmint-receipt.schema.json(not required; legacy
receipts validate unchanged).
Safety Boundary
- No archive migration and no hash change: BOM/newline tolerance affects
parse/read helpers only, never the sha256 functions. - Reconcile never edits zet content.
--approverecords the sha of the bytes as
they are; it does not rewrite the canonical. - Hard refusals (missing/non-mint receipt, id mismatch, non-canonical or
unparseable target, target.path mismatch) block before any classification, so a
swap-corrupted or foreign receipt is never laundered into a fresh integrity
record.mintstill refuses to re-mint an already minted zet.
Still Future
- The
remint-reconcile-batchtier is deferred (its receipt directory name is
reserved). Per-id human judgment on eachcontent_changeis the point of the
doctrine, so a bulk content-change ack is intentionally not shipped yet.
Verification
cd wom-kit
python -m pytest tests/ -q
python tools/check_public_privacy.py
python tools/check_release_readiness.pyUpgrade Notes
No data migration is required. The new command, BOM/newline parse tolerance, and
the LF write pin are additive; existing receipts and canonical files are
unaffected until you choose to run remint-reconcile. See
UPGRADE.md.
v0.3.161 - README Restructure
v0.3.161 - README Restructure
v0.3.161 is a docs-only release. It restructures the two root READMEs
(README.md
and
README.ko.md),
which had accreted one append per release for roughly 160 releases: a 49-line
"Earlier public baseline" ladder in Status, "What exists today" bullets that
had grown into 1,600-3,900-character run-on sentences, and a ~200-tag release
list in Versioning. No CLI, MCP, schema, or archive behavior changes.
What Changed
- Status now carries only: the current-baseline code block
(v0.3.161 pre-release), ONE previous-baseline line, and a single "Full
release history" pointer to CHANGELOG.md andwom-kit/docs/releases/. The
roadmap-snapshot paragraph and the not-production-ready sentence are
unchanged. The 49-line baseline ladder is deleted. - "What exists today" became a "What Exists Today" section with nine thematic
subsections: Archive core & lifecycle; Capture & intake; Retrieval & views;
Sharing & ZET previews; Privacy & redaction; AI-operator contracts &
runtime handoff; Provider integrations (Tiro / Notion / zettel edge writes /
object storage / IMAP); Credentials & setup guidance; Hygiene & release
tooling. The four monster bullets (credentials, Notion/export, IMAP, objet
links) were decomposed into short single-capability bullets. Every claim
from the old bullets survives (the claim-by-claim mapping is recorded with
the v0.3.161 decision log), and the section keeps its single pointer to the
WOM-kit Capability Matrix for the status-by-capability view. - "What does not exist yet" became "What Does Not Exist Yet" with the same
eleven one-line bullets; no non-goal claims were dropped. - Versioning keeps its two intro sentences and now states the current
checkpoint tag, that every release is tagged, and where the full tag
history lives (CHANGELOG.md, VERSIONING.md, and the GitHub releases page),
plus one compact paragraph naming the checkpoint baselines that earlier
release notes and upgrade guides still reference. The per-line tag list is
deleted. - README.ko.md received the same structure as a real Korean rendition in the
existing document's voice, not a machine-literal translation.
Maintenance Contract
To prevent re-accretion, v0.3.161 adopts a README maintenance contract,
recorded in the v0.3.161 decision log and as an HTML comment at the top of
each README's Status section: per release, update ONLY (1) the
current-baseline line, (2) the one previous-baseline line, and (3) for
feature releases, at most ONE thematic bullet under "What Exists Today".
Release history lives in
CHANGELOG.md
and the
release notes directory,
not in the READMEs.
Safety Boundary
- Docs-only: no new write path, no MCP tool, no provider call, and no schema
change. The version bump touches the usual three version files only. - All test-pinned README substrings (baseline strings, capability-bullet
fragments, pinned doc links, and the v0.2.5x tag ordering) survive the
restructure verbatim; the doc test suite passes unchanged.
Still Future
- No README content automation; the maintenance contract is a documented
human/agent rule, not an enforced tool gate.
Verification
cd wom-kit
python -m unittest discover -s tests
python tools/check_release_readiness.pyUpgrade Notes
No archive migration is required. Nothing changes for JSON consumers. If a
script scraped the deleted README baseline ladder or the Versioning tag list,
point it at CHANGELOG.md, VERSIONING.md, or git tag instead.