188 lines
12 KiB
Markdown
188 lines
12 KiB
Markdown
---
|
||
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 (corpus must exclude the file under test), doubled-page repair, trash-aware tools, delegated-write verification, honest per-area line accounting, 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).
|
||
|
||
Three 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.
|
||
- **The corpus MUST exclude the file under test, and you must assert it.** A census pointed at
|
||
the directory that *contains* the page proves the page is held by itself: ten era pages
|
||
reported `0 sole-record lines`, which reads as "pure duplication, trash it". With the file
|
||
excluded, the same pages held **14–63 sole-record lines each** — campaign ledgers that exist
|
||
nowhere else. A tool's own docstring promise ("excluding it") is not a check: print the
|
||
corpus size, assert the target is absent, and treat `0 unheld` on a long page as a tool bug
|
||
until proven otherwise. A mechanical census is a scout: its verdict is a lead, not a finding.
|
||
|
||
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 page-tail becomes 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.
|
||
Generator bugs 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>]]`); and **no dead-row sweep**, so deleting a page leaves
|
||
an index row that fails the link check — the generator must drop rows for absent pages too.
|
||
|
||
## 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.
|
||
|
||
Splitting an over-long page to satisfy a ceiling is usually line-NEGATIVE: one measured split
|
||
turned a 700-line page into 488 + 306 across five files (+23 for the folder net of stub
|
||
deletions), because frontmatter blocks, pointer sections and connective prose are charged per
|
||
file while the facts move for free. Roll the split back, carry `okf-exempt: length` with the
|
||
reason, and keep the stub pages deleted.
|
||
|
||
## 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.
|
||
|
||
A `PUT` that spans a multi-line statement can silently drop neighbouring assignments
|
||
(auto-repaired boundary echo); after editing a constants block, `grep -n "^[A-Z_]* = "` and
|
||
run a syntax probe before executing.
|
||
|
||
## 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.
|
||
Push with an explicit refspec (`HEAD:refs/heads/<pr-branch>`) — a worktree's own branch name
|
||
is not the PR branch, and `git push origin HEAD` opens a stray branch.
|
||
- 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.
|
||
|
||
## 12. Report line counts per area, never one net
|
||
|
||
"Compaction" measured against the merge-base **per area**: docs / `scripts/` / `AGENTS.md`.
|
||
A single headline number hides that tooling and prose move in opposite directions — one
|
||
campaign reported "−1,849 vault lines" while the vault was flat and the growth was the two
|
||
enforcement scripts that made the law mechanical. Expect a mature one-owner vault to land
|
||
**docs flat**: the reductions available are deletions of *restatement* (duplicated pages,
|
||
transient deploy chatter in `log.md`, `AGENTS.md` bullets whose numbers an endpoint page owns)
|
||
and they are roughly cancelled by the cost of the citations the verification pass adds. When
|
||
asked to shrink further, show the menu — drop the tools, drop the citations, or accept content
|
||
loss — instead of inventing cuts. `log.md` compaction rule that survived census: drop a line
|
||
only when it matches a transient shape (`/health`, `deployed`, `restart`, `systemctl start`)
|
||
**and** every fact token in it is held by an owner page; a token-held line can still be the
|
||
only record of *when* an incident happened.
|