Files
command-center/agent/managed-skills/vault-one-fact-one-owner-dedupe/SKILL.md
T

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.md owns incidents and laws only. A swap is an Eras row, never a log entry.
  • AGENTS.md carries 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 (.gitignore has .AGENTTRASH/). Move with plain mv into the repo-root dated dir, e.g. .AGENTTRASH/<YYYYMMDD>-topic/LLM/. git mv a tracked page into it stages a rename into an ignored path (index entry the next agent cannot see); undo with git restore --staged <src> <dst> and let the deletion be the change.
  • Never rm, never git 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 page seam 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)

  1. Commit your work in atomic groups before merging (a dirty tree cannot merge cleanly).
  2. 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 as git rm.
  3. Resolve a content block with write conflict://<id> (@ours / @theirs / explicit text), then git add the file — resolving content does not stage it, and git commit refuses with "you have unmerged files".
  4. git * --no-verify is forbidden here; commit hooks are fine.
  5. Sweep for dead paths left in AGENTS.md: grep -oE "[A-Za-z0-9._-]+\.md" AGENTS.md | sort -u then find each. Expect false positives from names hard-wrapped across two lines — confirm each with ls before "fixing".

9. Verify, open, land

  • ./tests/run.sh (full suite) + bats tests/docs_completeness.bats + python3 scripts/check-vault-links.py serverAppConfigs-docs → must print 0 unresolved links.
  • Env trims: prove value-neutrality mechanically — git show HEAD:scripts/<svc>.env | grep -E '^[A-Z_]+=' | sort vs the working copy, diff must be empty, then run that service's seam. Warn that scripts/<svc>.env is 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 the cwd: / session: footer, then xdg-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).