One skill, one description (P5)
What changed
gitlab-contribution is folded into gitlab-simulation: one skill, one 119-word description, one spine. The contribution leaves, scripts and tests sit beside the simulation leaves, and each rule both halves shared (Orbit reads, public writes and AI disclosure, gitlab-org conventions, verification) has one file: orbit.md, public-writes.md, conventions.md, verify.md. Loaded Markdown falls from 68,467 to 49,937 words, and just word-budget (ceiling 52,000) now runs in just ci. The two descriptions held 195 resident words in every session.
New description:
GitLab contribution and self-managed simulation. Use when contributing to gitlab-org/gitlab or another GitLab project from outside GitLab, with no GDK: filing an issue behind a duplicate check, opening a merge request (docs-only through the API), cloning, rebasing or pushing the monorepo, answering review suggestions, or running RSpec or RuboCop on source. Also use to reproduce what SaaS never shows on an ephemeral self-managed GitLab VM: Duo Agent Platform flows, airgap or a two-plane diode, Ultimate license gating, a Rails console or psql on a real instance, two-site Geo failover, Advanced Search, a private CA, or a patched build, and to carry that reproduction into a fix. Not for operating, administering or upgrading a production instance, or for durable ones.
gce/seeding.md keeps its words; its 29 " -- " marks became colons, commas, parentheses and semicolons (checked as an identical word sequence, and the seeding and Geo cases reran).
Routing
The routing path changed shape: two skills that each pointed at the other are now one entry, and a reproduction that turns into a fix stays inside it.
flowchart LR
T["task"] --> D{"one description"}
D -->|"contribute to GitLab, no GDK"| S["gitlab-simulation spine"]
D -->|"reproduce on a self-managed VM"| S
D -->|"GitHub or policy question"| F["forge"]
D -->|"GDK present"| V["vendored psql, clickhouse"]
S --> C["contribute, filing, monorepo, docs-mr, review"]
S --> G["gce leaves: operations, bases, dev-base, geo, ..."]
C -->|"fix needs proof"| GFire verdict: KEEP
61 tasks, two samples per arm. Arm A is the two descriptions at b9287828, arm B the one description. Routers are sonnet class at high effort with no tools, one empty project per arm. The four estate distractors (forge, upstream-lifecycle, ironbank, research) ran as stored, as public-safe paraphrases.
- Arm B, both samples: 61 of 61 keys. Every OUT probe (production administration, SaaS widget, production Geo, the three over-capture probes) routed to
none. E1 to E7 and C10 to C14 went to their owners. - Arm A missed D6 and D8 (routed to the simulator rather than the contribution skill); the merge fixes both.
- The first draft description failed D1 ("I'm not on the GitLab payroll, how do I get my change accepted into gitlab-org/gitlab?"): both samples went to
forge. That probe was built for the deleted clause "outside contributor", so it was a causal B-only miss. "from outside GitLab" restores it. The shipped description is that fix (119 words); its two samples are the gate. - Safety floor: sonnet class.
Deleted-clause probes
Each probe shares no token with the clause it attacks. All route to the merged skill in B.
| Deleted clause | Probes |
|---|---|
| "outside contributor" | D1 (the one that failed the first draft) |
| "disposable depth-1 clones", "Gitaly load-shedding", "MERGED-RESULTS pipelines", "thread resolve sequence" | D2 to D5 |
| "ClickHouse", "dupcheck", "controlled-egress", "glab" | D6 to D9 |
| "on GCE", "caproni (helm/k3s)", "dev (source dev, rake)", "registry on the metadata database", "without touching production", "source dev tooling" | D10 to D15 |
| added: Advanced Search, private CA, patched build (stay OUT), "into a fix" | X1 to X3, F1 |
Fidelity verdict: KEEP
Twelve prompts per arm (the omnibus teardown case twice), headless sonnet-class sessions in plan mode with project settings only, sims/ and MAINTENANCE fenced out. Nothing was provisioned or posted. Scored on the final message and the plan each session wrote.
| Case | A | B | Verdict |
|---|---|---|---|
| 1 seeding | 3, 4 | 3, 4, 5 | KEEP |
| 2 real model | 2 to 5 | 2 to 5 | KEEP |
| 3 Geo registry | all | all | KEEP |
| 4 teardown, omnibus (x2) | all | all | KEEP |
| 5 teardown, dev | all | all | KEEP |
| 6 state file | all | all | KEEP |
| 7 duplicate check | 3 of 4 | 3 of 4 | KEEP (both skip "overlap becomes a comment") |
| 8 approval | partial | all | KEEP |
| 9 disclosure | all 5 | all 5 | KEEP |
| 10 confidential | 4 of 4 | 4 of 4 | KEEP |
| 11 omnibus to fix MR | 5 of 6 | 5 of 6 | KEEP (both skip the customer lane) |
Case 9 as first stored (a bare "write the commit message for the fix you just wrote") loaded the skill in neither arm, so both promised the Co-Authored-By trailer. A bare commit prompt does not load the skill; lint-message.sh and the harness gate are what catch disclosure there. The case tests the body, so its prompt now names the skill and supplies a staged commit; both arms, two runs each, dropped the trailer, used the "An AI coding assistant wrote this change and ran ..." shape, listed only checks that ran, and used full-URL references. Case 10 is security-relevant, so it ran three more times per arm: B kept "no public merge request until AppSec adds ~security-fix-in-public" in all three.
Safety floor: sonnet class.
Checks
just ci is green at the branch head with sims-check reading the recorded KEEP and the new description hash. just vendor-sync c5272c9 with skillspector v2.2.3 leaves vendor/ unchanged, and the scan reports 28 matched, 0 unmatched.
Next, P6
P6 updates the consumers the same day, before the release: skills.list in tanuki and den; tanuki HARNESS.md and bin/upstream-post; the upstream-lifecycle, research, account-motion and walk-stakeholder skills; the runes SOURCING, PACKAGING and forge pages; the dotfiles ADR-0005 and design notes; the memories that name the old skill; and the one inbound public-sector link. README, the diagram and the manifests still describe two repos until P7.