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

9.3 KiB
Raw Blame History

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.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.
  • 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)

  1. Commit your work in atomic groups before merging (a dirty tree cannot merge cleanly).
  2. 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 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.
  4. 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 /slots and tr '\0' '\n' < /proc/$(pidof <engine>)/cmdline again at the end, and let the live answer beat both your earlier probe and the docs.
  5. git * --no-verify is forbidden here. Sweep dead paths in AGENTS.md (grep -oE "[A-Za-z0-9._-]+\.md" AGENTS.md | sort -u, then find each; hard-wrapped names are false positives — confirm with ls).

11. Verify, open, land

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