9.3 KiB
name, description
| name | description |
|---|---|
| vault-one-fact-one-owner-dedupe | "Collapse per-payload/per-attempt vault pages into one owner page per endpoint without losing a fact: port-before-cut orphan census, doubled-page repair, trash-aware tools, delegated-write verification, index regeneration, and the merge-resolution order." |
One fact, one owner: dedupe the vault without losing a fact
Use when a repo has accumulated one page per attempt/era (runbooks, bench logs, swap
narratives) and the law is one page owns one thing. Applies to
serverAppConfigs-docs/ (Obsidian vault, AGENTS.md → Documentation Policy), but the
mechanics are general.
1. Decide ownership before touching files
- Endpoint/service page owns: current shape, Eras table (window | payload | runtime | shape | replaced because | rollback), Laws, wire contract, files.
- Technique page owns the mechanism and the arithmetic — no per-endpoint throughput table or boot ledger.
log.mdowns incidents and laws only. A swap is an Eras row, never a log entry.AGENTS.mdcarries operator-critical numbers + cross-border laws; everything else links.- A retired endpoint still gets one locator page: era windows quoted verbatim from each era page's own banner, no numbers restated, and any date conflict recorded as an open question instead of silently resolved. Never pick a winner between two primary sources.
2. Single-writer rule (the mistake that costs the most)
Facts get ported into the owner page before they are cut anywhere else. So while any
subagent may port facts, only ONE writer may touch the owner pages. Spawn children with an
explicit DO NOT EDIT <owner pages> and require a needsPort list in their report instead
of letting them append. Send the narrowing message early — a child already editing them has
to be reconciled by hand.
3. Port-before-cut census (proves nothing was lost)
For every deleted line, extract distinctive keys and check them against the vault minus the
file being rewritten. Any line with no surviving key is an orphan → port, or accept
deliberately and say so. Use scripts/port-census.py <new-dir> <git-HEAD:path|file> (numbers
with ≥ 4 int digits, decimals, flags, paths, backticked spans, --, ALL-CAPS, or ≥ 2 hyphens).
Two calibration traps:
- Prose words are not facts. A hyphen or trailing period inside a word (
long-standing,deployed:) was counted as a tracked token while real identifiers hid behind it; that noise buried 300 real lines under 979 fake ones. Strip punctuation, require fact shape. - The corpus decides the question. Corpus = the new file only → "what did this rewrite drop?" Corpus = whole vault → "is the dropped fact orphaned repo-wide?" Ask one at a time.
Also grep AGENTS.md separately — a fact it carries is not an orphan.
4. When a page is the same page twice
Signature: a page whose wc -l is ~2× its HEAD size, with a second frontmatter block in
the body whose closing --- is glued onto the end of a body/timestamp line. Cause chain: a
frontmatter rewrite that re-reads the file it just wrote (so its own output becomes the body),
then a fence-repair pass keyed on line == "---" that cannot see a glued fence and inserts a
fence mid-page, so the next pass treats the tail as a new document.
Un-double rule that worked: find the first frontmatter's close (accepting a glued fence), then
the first type: line after it that has more keys (title|description|tags|timestamp) behind
it — that is the second copy, and a stray type: in a code block has no key run. Keep the copy
whose body is a superset (compare after stripping the dropped copy's own frontmatter),
un-glue the fence, and prove 0 census losses before writing. Refuse to guess: if the halves
differ materially, stop.
5. .AGENTTRASH is not vault content
Every tool that walks the vault must exclude it — both the scan set and the resolution
index. The link checker resolving links into the trash turns "live page points at a
deleted page" into a false PASS, which is worse than a false alarm. The docs_completeness
seam's find must carry -not -path '*/.AGENTTRASH/*' in both walks, or a trashed page
demands an index row and a trashed page can satisfy the docs requirement for a live service.
Trash is gitignored; move with plain mv into the dated dir. git mv into an ignored path
stages a rename the next agent cannot see (git restore --staged <src> <dst>). Never rm,
never git clean -f.
6. Rewire + regenerate indexes mechanically
grep -rnoE "\[\[(page-a|page-b|...)\]\]" --include="*.md" serverAppConfigs-docs | grep -v AGENTTRASH
grep -rnoE "\((page-a|page-b|...)\.md\)" --include="*.md" serverAppConfigs-docs | grep -v AGENTTRASH
Both wikilinks and markdown path refs must be repointed (Research See Also lists and bench
pages are where path refs hide). Then regenerate index rows with a script, never by hand.
The generator bug to check for: presence tested as a bare stem substring — a page whose
name prefixes another's (frank-medium inside frank-medium-rocmfpx) then silently never
gets a row. Test the wikilink form [[<stem>]].
7. Enforce the law with a seam
tests/docs_completeness.bats: derive the live set instead of hardcoding it — an endpoint is
live when scripts/ holds both <name>.env and <name>.service; fail if any
<name>-*.md page exists, and guard against a vacuous derivation (derived -ge 3). A
re-created era page then cannot pass tests. For the length hint, prefer derived exemptions
(live endpoint, or a retired-endpoint tag: a campaign ledger that exists nowhere else is
owned by the page that measured it) plus one documented per-page opt-out; never let a size
ceiling force a ledger to be split across two owners.
8. Edit-tool hazard (costs the most time)
Body rows beginning with - or + are op markers. The documented +- escape writes a
literal + — do not use it. Safe rewrites inside an existing bullet: keep the - opener
line untouched and PUT only the indented continuation lines, or convert the note to a nested
bullet whose body lines start with two/four spaces. If a wrapped [[Two Word Page]] shows up
as an unresolved link, the target was hard-wrapped across lines — unwrap it.
9. Delegated writes: verify bytes, never narrative
Measured in one campaign, all four failure modes live:
- A subagent killed by the runtime limit yielded a 495-line owner page, a verified era table and a byte census — the file did not exist and its table was self-referential.
- Another wrote its output to disk and died without reporting, leaving a rewritten
log.md; the census showed it had dropped laws held nowhere else. - Verdict tables claimed "0 sole records / no contradictions" while missing the contradiction sitting in their own evidence row, and invented bullet IDs — one renamed a bullet to the opposite claim.
- A scout called accurate docs "WRONG" because the docs said
wip: "down".
Rules: after ANY delegated write run wc/ls + the orphan census yourself; re-grep every
claimed holder before cutting a line; treat cancelled/timed-out yields as unverified no
matter how concrete they read; and re-read the source bullet before trusting a table's ID.
10. Merge order (a long-lived branch under a moving main)
- Commit your work in atomic groups before merging (a dirty tree cannot merge cleanly).
- Inspect the incoming commits:
git log --oneline --stat HEAD..origin/main. If they modify a page you deleted, port their additions into the owner page, then merge and resolve the modify/delete asgit rm. - Resolve a content block with
write conflict://<id>(@ours/@theirs/ explicit text), thengit addthe file — resolving content does not stage it. - Re-probe live state after the merge, before you write any number. A campaign that
reported fleet shapes at the start was made stale by a mid-campaign reslice on main: probe
/slotsandtr '\0' '\n' < /proc/$(pidof <engine>)/cmdlineagain at the end, and let the live answer beat both your earlier probe and the docs. git * --no-verifyis forbidden here. Sweep dead paths inAGENTS.md(grep -oE "[A-Za-z0-9._-]+\.md" AGENTS.md | sort -u, thenfindeach; hard-wrapped names are false positives — confirm withls).
11. Verify, open, land
./tests/run.sh(full suite) +python3 scripts/check-vault-links.py serverAppConfigs-docs→ must print0 unresolved links, and the OKF linter0 violations.- Env trims: prove value-neutrality mechanically (
git show HEAD:scripts/<svc>.env | grep -E '^[A-Z_]+=' | sortvs working copy, diff empty).scripts/<svc>.envis a deploy-planner path: a comment-only trim makes the next service-scoped deploy restart a healthy endpoint. - Open the PR through the API with a 600-perm curl config (managed skill
gitea-api-pr-operations), end the body with thecwd:/session:footer, thenxdg-open <PR-URL>the instant it exists. Never merge without Frank's current explicit OK. - Docs-only change ⇒ no deploy. Fast-forward the deploy checkout only
(
git -C ~/Projects/serverAppConfig merge --ff-only origin/main). - Say what was NOT done, in the PR body: soft lint tallies left (missing
## Citations), conflicts left standing, repo config that was never installed on a host.