6.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, index regeneration, the one-page seam, 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.
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 (integers ≥ 4 digits with commas stripped, decimals with ≥ 2 fraction digits) and check them against the whole vault minus the file being rewritten. Any line with no surviving key is an orphan → port, or accept deliberately and say so.
num = re.compile(r'\d[\d,]*(?:\.\d+)?')
def keys(line):
out = set()
for n in num.findall(line):
i = n.split('.')[0].replace(',', '')
if len(i) >= 4: out.add(i)
if '.' in n and len(n.split('.')[1]) >= 2: out.add(n.replace(',', '')[:6])
return out
# orphan = deleted line whose keys are absent from corpus(vault minus target file, minus .AGENTTRASH)
Expect ~30 orphans per 1,000 deleted lines, of which ~40 % are process trivia recoverable
from git (PR/commit hashes, per-boot log lines). Port the measurement/law ones to the owner
page's Laws/Eras, and list the accepted losses in the commit message and PR body. Also grep
AGENTS.md separately — a fact it carries is not an orphan.
4. Deletion mechanics
- Trash is gitignored (
.gitignorehas.AGENTTRASH/). Move with plainmvinto the repo-root dated dir, e.g..AGENTTRASH/<YYYYMMDD>-topic/LLM/.git mva tracked page into it stages a rename into an ignored path (index entry the next agent cannot see); undo withgit restore --staged <src> <dst>and let the deletion be the change. - Never
rm, nevergit clean -f.
5. Rewire + 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
ubatch-style bench pages are where path refs hide). Then regenerate index pages with a
script that walks the vault (os.walk, skip .obsidian/.AGENTTRASH, one row = [[basename]]
- first-sentence description stripped of markdown links) rather than hand-editing rows — the
docs index links every docs pageseam fails on any row left behind.
6. 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 in the vault, and guard against a vacuous derivation
(derived -ge 3). A re-created era page then cannot pass tests.
7. 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.
8. 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 first:
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, andgit commitrefuses with "you have unmerged files". git * --no-verifyis forbidden here; commit hooks are fine.- Sweep for dead paths left in
AGENTS.md:grep -oE "[A-Za-z0-9._-]+\.md" AGENTS.md | sort -uthenfindeach. Expect false positives from names hard-wrapped across two lines — confirm each withlsbefore "fixing".
9. Verify, open, land
./tests/run.sh(full suite) +bats tests/docs_completeness.bats+python3 scripts/check-vault-links.py serverAppConfigs-docs→ must print0 unresolved links.- Env trims: prove value-neutrality mechanically —
git show HEAD:scripts/<svc>.env | grep -E '^[A-Z_]+=' | sortvs the working copy, diff must be empty, then run that service's seam. Warn thatscripts/<svc>.envis a deploy-planner path: a comment-only trim makes the next service-scoped deploy restart a healthy endpoint for identical values. - 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. - Docs-only change ⇒ no deploy. Fast-forward the deploy checkout only
(
git -C ~/Projects/serverAppConfig merge --ff-only origin/main).