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

153 lines
9.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: vault-one-fact-one-owner-dedupe
description: "\"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
```bash
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.