Skip to content
Cockpit0.29.0
A snapshot: read at commit 707a04c7ce8 on 2026-10-18T00:06:01Z by majordomus 0.29.0. Nothing on this page is read again. See it live

project.a-personal-layer-changes-nothing-shared@1

A personal layer changes nothing shared

What it is

URI
majordomus://rule/project.a-personal-layer-changes-nothing-shared@1
Kind
rule
Identity
project.a-personal-layer-changes-nothing-shared@1
Path
.ai/repo/rules/project/a-personal-layer-changes-nothing-shared.v1.md
Provenance
declared
Media type
text/markdown
As a capability
rule.project.a-personal-layer-changes-nothing-shared@1

Front matter

{
  "id": "project.a-personal-layer-changes-nothing-shared",
  "version": 1,
  "kind": "rule",
  "title": "A personal layer changes nothing shared",
  "description": "A layer installed with --personal is one person's own in a shared repository; it changes no tracked file and nothing git status shows, every writer covers before it writes, the claim is verified by the command that makes it, and --shared hands the layer to the team.",
  "statement": "A personal layer MUST leave `git status --porcelain --untracked-files=all` as it was and MUST NOT write a tracked file; every writer outside the layer MUST cover a path through `mj_personal_guard` before writing it and MUST refuse a tracked one; `init --personal`, the installer's `--personal` and `doctor` MUST each verify the result and fail when it does not hold; whether a layer is personal MUST be read from the exclude block and stored nowhere else; `init --shared` MUST remove the cover and MUST NOT commit.",
  "status": "active",
  "class": "blocking",
  "depends_on": [
    "project.distribution-canonical@1",
    "project.portable-shell@1",
    "project.no-claim-without-test@1"
  ],
  "tags": [
    "install",
    "adoption",
    "discovery",
    "enforcement"
  ],
  "x-majordomus": {
    "tests": [
      "test/cases/2979_a_personal_layer_changes_nothing_shared.sh",
      "test/cases/86_installer_platform.sh"
    ]
  }
}

Content

---
id: project.a-personal-layer-changes-nothing-shared
version: 1
kind: rule
title: A personal layer changes nothing shared
description: A layer installed with --personal is one person's own in a shared repository; it changes no tracked file and nothing git status shows, every writer covers before it writes, the claim is verified by the command that makes it, and --shared hands the layer to the team.
statement: A personal layer MUST leave `git status --porcelain --untracked-files=all` as it was and MUST NOT write a tracked file; every writer outside the layer MUST cover a path through `mj_personal_guard` before writing it and MUST refuse a tracked one; `init --personal`, the installer's `--personal` and `doctor` MUST each verify the result and fail when it does not hold; whether a layer is personal MUST be read from the exclude block and stored nowhere else; `init --shared` MUST remove the cover and MUST NOT commit.
status: active
class: blocking
depends_on: [project.distribution-canonical@1, project.portable-shell@1, project.no-claim-without-test@1]
tags: [install, adoption, discovery, enforcement]

x-majordomus:
  tests: [test/cases/2979_a_personal_layer_changes_nothing_shared.sh, test/cases/86_installer_platform.sh]
---

# Rationale

A team adopts a tool on evidence, and evidence comes from use. Until this rule a developer
could not use the tool on real work without changing files the whole team shares, so a
personal trial became a proposal before there was anything to propose. The hand-written
exclude file that stood in for an answer was verified by nothing, written past by `update`
and `capture install`, and unreadable to the executable, which enumerates tracked files.
"I changed nothing you share" is a claim about other people's work, and a claim like that
is checked by the command that makes it. ADR 0145.

# Required behaviour

1. **One cover, git's own.** The layer and every path written for it are listed in a marked
   block of `$GIT_COMMON_DIR/info/exclude`, anchored, one a line. Lines outside the block
   are never changed. The tracked `.gitignore` is not written.
2. **No stored mode.** `mj_personal_active` reads the block. No manifest key, policy key,
   environment variable or state file says "personal".
3. **Cover before write.** A writer outside the layer calls `mj_personal_guard <path>…`
   before it writes. Under a personal layer the guard covers the path and the staging name
   beside it; elsewhere it does nothing, so the call is unconditional.
4. **A tracked path is the project's.** It is refused with exit 15 while the command is
   still planning, before any path is written and before any advice to force it; a dry run
   refuses the same way. `doctor` does not hold such a file to a stamp or a bootstrap line.
5. **Verified where it is claimed.** `init --personal` exits 10 when the status after
   differs from the status before. The installer compares the status itself. `doctor`
   reports `OK personal` or names what is tracked, uncovered or shown.
6. **The project's README is not asked to name the bootstrap** under a personal layer.
7. **Discovery is derived.** With the manifest on disk and tracked nowhere, the executable
   walks the work tree; no option selects it.
8. **Sharing is explicit.** `init --extend --shared` removes the block, writes the two
   `.gitignore` lines of a plain init, prints the paths that are now the repository's to
   commit, and commits nothing. It refuses a layer that is not personal. A path that is
   one person's by its own name (`*.local`, `*.local.*`) stays covered.

# Failure behaviour

`test/cases/2979_a_personal_layer_changes_nothing_shared.sh` fails when, in a repository
that already holds uncommitted work, `init --personal`, `update`, `capture install` or an
`--extend` changes `git status` or the tracked `.gitignore`; when a projection target the
policy names is not in the block; when `update` writes any target after meeting a tracked
one; when `doctor` does not name a tracked file of a personal layer, or fails the README;
when a tracked layer is accepted as personal; when a linked worktree shows the layer; when
the executable lists no rule of an untracked layer; when `--shared` leaves the block,
commits, or accepts a layer that is not personal; or when the installer reports success
without verifying. `test/cases/86_installer_platform.sh` fails when the installer's parser
accepts an option its `--help` does not document.

# Verification

```
MJ_TEST_JOBS=2 bash test/run.sh 2979_a_personal_layer_changes_nothing_shared 86_installer_platform
cargo test --manifest-path apps/majordomus-cli/Cargo.toml --doc layer_is_untracked
```