# Rule 12 - An artifact that travels names nothing only its birthplace has

## Purpose

Akinator's whole premise is that a document asserting things that are not there
is a critical defect. It was shipping 22 of them per install.

Every file in the Codex pack carried a banner naming its generator:

```
Generated by `scripts/build_codex_pack.py` from `skills/everything/references/akinator-plan.md`.
See `rules/07-codex-pack-is-generated.md`.
```

All three of those paths are real - **here**. The installer copies those files
into other repositories, where none of them exists. A bare repository that ran
the old Codex install script and then the coverage checker got **22 HIGH
findings on its first run**, every one on a file the plugin had just written.
The portable contract failed too, on the subtler half: `build_codex_pack.py` is
not a path, but it is still a file the host repository does not have.

It came back twice more, and each time outside this rule's old scope:

- **The Cursor router copy.** The README told Cursor users to copy
  Akinator's own Cursor router into their repository - a file naming 21 paths
  that exist only in Akinator's checkout. It was never in scope, because the
  scope listed only the Codex pack.
- **The tool paths.** The skill's procedure ran its tools by repo-only paths
  (the ledger tool under this checkout's `scripts/` directory), inside fenced blocks the body test
  exempted as illustrative. A command an agent is told to run is an
  instruction, not an illustration, and in a host repository it failed.

Evolved on 2026-09-18 (the recorded distil decision in
`.ai/ledger/decision/distil-a-generated-artifact-that-travels-named-56871dcd648a.md`)
rather than adding a rule beside it: three sightings, one defect.

The reason this survived every test is worth more than the bug. Twenty-one tests
covered the pack, including one named
`test_portable_contract_names_no_repo_relative_paths`. All of them looked at the
pack **from inside this checkout**, where every path resolves. None looked at it
from where it lands. A claim is only true relative to a tree, and a test that
never changes trees cannot check one.

## Applies to

- **In scope:** everything the installers copy - the one skill
  (`.agents/skills/akinator/` with its references and tools), the portable
  contract `.agents/AGENTS.md`, and the Cursor rule `.agents/cursor/akinator.mdc`
  - and any future artifact installed into another repository. Also in scope:
  the canonical skill's references under `skills/everything/references/`, which
  travel inside the Claude plugin and are projected into the pack. The scope is stated as what ships, not as what might one day
  ship, because a rule claiming more than its mechanism covers is the shape of
  defect this rule exists to stop.
- **Out of scope:** files that stay here. `context/stack.md` and
  `context/components.md` name their generators by path and should, because the
  generator is in the same tree and the reader can run it.
- **Out of scope, deliberately:** `templates/**`. The installers do not copy it
  - a target repo gets the pack and the contract, nothing else - and a template
  naming `scripts/<extractor>.py` is a placeholder the host fills in, not a
  claim. If templates ever start being installed, they come into scope and need
  the same test.

## Mandatory rules

1. A travelling artifact **names no file by path or by filename** - not its
   generator, not its source, not a rule it obeys. Every such name is a claim
   about a tree the author has never seen.
2. It states instead **where it came from** and **how to get a fresh copy**.
   Both halves. An origin with no refresh path is a dead end, and a stale copy
   nobody can update is no better than a generator that is not there.
3. Its correctness is tested **from the destination** - installed into a scratch
   repository, then checked - not only from this one.
4. Where the artifact must reference this repository's own conventions, it
   describes them ("this repository's rules directory, whatever it is called")
   rather than naming a path, and defers to the host's names. See
   `rules/04-routers-stay-thin-and-synced.md` on adopt-never-impose.
5. This binds the **body** as well as the banner. Fixing only the banners left
   twenty path references in eleven skill bodies - `templates/adr.md`,
   `evals/newcomer/README.md` and friends - each of them a file that exists only
   in an Akinator checkout, inside a document whose purpose is to be read
   somewhere else. A skill that needs to point at Akinator's own material
   **describes** it ("Akinator's ADR template") instead.
6. A tool the skill tells an agent to run is invoked as
   `<skill>/scripts/<tool>.py`, where `<skill>` is the skill's own directory -
   **wherever it appears, fenced or not**. A command is an instruction, not an
   illustration; the fence exemption for illustrative paths never covers it.
7. A path relative to the travelling file that lands **inside the pack** -
   a reference's link up to the skill's own SKILL.md, or the skill's link down
   to its procedure reference - is allowed, because it travels
   with the file and is true wherever the file lands.

## Prohibited patterns

```
<!--
GENERATED FILE - DO NOT EDIT BY HAND.
Generated by `scripts/build_codex_pack.py` from `skills/everything/references/akinator.md`.
See `rules/07-codex-pack-is-generated.md`.
-->
```

Three claims, all false wherever this file actually lives.

```
<!--
Regenerate with `build_codex_pack.py`.
-->
```

Still wrong, and harder to spot. Dropping the directory does not make the file
exist in the host repo - it only makes the falsehood shorter. This exact form
shipped in the portable contract *because* a test filtered on `"/" in name`.

```
<!--
Installed from the Akinator plugin.
-->
```

Wrong in the other direction: honest about origin, useless to anyone holding a
stale copy.

## Correct pattern

```
<!--
DO NOT EDIT BY HAND.
Installed from the Akinator plugin - the canonical akinator-plan skill.
No generator is named by path: this file travels into repositories
that do not have one, where naming it would be a false claim.
To update: reinstall Akinator, or regenerate inside an Akinator
checkout. Local edits here are replaced either way.
-->
```

The origin is named. The refresh path is named, in both the host case and the
checkout case. Nothing is asserted to exist in the tree holding the file - and
the banner says *why*, so the next contributor does not "helpfully" add the path
back.

## Enforcement

- Mechanism: `tests/test_codex_pack.py::test_the_installed_pack_leaves_a_target_repo_clean`
  - writes the whole pack (skill, portable contract, Cursor rule) into a scratch
  repository containing nothing else, where each lands, and runs the coverage
  checker there, failing on any finding at medium or above. This is the test
  that changes trees, and it is the one that matters.
- Mechanism: `tests/test_installer.py` - runs the **real** installers (sh
  everywhere, PowerShell on Windows) into a scratch repository with a stub
  claude, then runs the strict checker there.
- Mechanism: `tests/test_codex_pack.py::test_pack_banners_name_no_file_the_host_repo_will_not_have`
  - asserts no banner in the plan names any file, by path or by bare filename.
- Mechanism: `tests/test_codex_pack.py::test_pack_bodies_name_no_path_the_host_repo_will_not_have`
  - the same property for the **bodies**, which was the larger half: fixing the
  banners left twenty path references across eleven skills. Fenced blocks are
  exempt, because an illustrative path inside an example reads as illustrative;
  a path that resolves inside the pack is allowed.
- Mechanism: `tests/test_codex_pack.py::test_every_markdown_link_in_the_pack_resolves_inside_it`
  - every link in a packed `.md` or `.mdc` file resolves to another file of the
  pack.
- Mechanism: `tests/test_plugin_structure.py::test_the_skill_runs_its_tools_from_its_own_folder`
  - tool invocations use `<skill>/scripts/<tool>.py`, fenced or not.
- Mechanism: `skills/everything/scripts/akinator_coverage.py` - `check_generated`, whose vendored
  branch requires an origin **and** a refresh path, and still fails any banner
  that names a generator which does not resolve; `.mdc` files are scanned, so a
  Cursor rule counts as a router and is link-checked.
- Type: unit and integration tests, run with the suite and in CI.
- How it fails: the first names the check, path and message of every finding the
  install would produce; the second names the offending file and token.
- Last observed passing: 2026-09-18

**Never a git hook** - see `rules/05-no-git-hook-complication.md`.

## Exceptions

None. If a travelling artifact genuinely needs to point at something, it points
at it by description, or at a stable external URL, never at a repository path.

## Related

- Ledger: `.ai/ledger/failure/a-generated-artifact-that-travels-named-56871dcd648a.md`
- Memory: `memory/2026-08-30-a-claim-is-only-true-relative-to-a-tree.md`
- Docs: `docs/adr/0007-vendored-artifacts-declare-origin-not-generator.md` - why
  the checker gained a vendored branch instead of skipping `.agents/`
- Docs: `docs/adr/0009-one-skill-one-command-one-installer.md` - the one skill
  and the one installer whose copies this rule now scopes
- Evals: `evals/results/2026-08-30-06-anti-gaming.md` - the red-team run that
  found this, twice, independently
- Rules: `rules/07-codex-pack-is-generated.md` - the pack is generated;
  this rule constrains what the generated form may say
- Rules: `rules/11-invariants-ship-with-a-mutation-test.md` - the same
  asymmetry, one level up: a test that cannot fail proves nothing

## Definition of done

- [x] The constraint is stated as a testable proposition.
- [x] The enforcement mechanism exists in the tree and is named by path.
- [x] The mechanism is not a git hook.
- [x] Prohibited and correct patterns are shown, including the near-miss form.
- [x] The exception path is named - here, that there is none.
- [x] The rule is indexed and reflected in every router.
