AGENTS.md ยท diff

git:20260901.b460e16 to git:20260901.7c8dac8

21 added, 5 removed. Audit A to A.

# AGENTS.md - msp-skills
Instructions for AI agents working in this repository. Read this before making
changes; see [`CONTRIBUTING.md`](CONTRIBUTING.md) for the human-facing version.
## What this repo is
A monorepo of MSP vendor connectors. Each `skills/<slug>/cli` is a Go CLI + MCP
server **generated by [cli-printing-press](https://github.com/mvanhorn/cli-printing-press)**
and then carries connector-specific edits. The press is upstream and refreshes
independently; we pull new binaries and **reprint** connectors over time.
## Before you reprint or re-onboard a connector (READ THIS)
A reprint regenerates the `DO NOT EDIT` files under `skills/<slug>/cli` and can
**silently clobber** a hand-fix - the build and tests stay green while the
live-API behavior the fix encoded is gone (this is how the axcient `id_`
primary-key fallback got reverted; see [`docs/reprint-survival.md`](docs/reprint-survival.md)).
**Whenever you regenerate, reprint, re-onboard (msp-skills-publish UPDATE), or
bulk-overwrite a connector's `cli/` tree, you MUST:**
1. **Before** generating, read the connector's recorded hand-fixes so you know
what to preserve:
```bash
python3 tools/maintainer/check_handfixes.py --brief --slug <slug>
```
Preserve every entry it lists (3-way merge, don't blind-overwrite). For a
targeted fix, prefer a surgical change over a full reprint.
2. **After** generating, prove nothing regressed:
```bash
python3 tools/maintainer/check_handfixes.py --slug <slug>
```
If it fails, restore the missing hand-fix (the failure names the file +
marker) before committing. CI runs this same gate per-skill and will block
the merge otherwise.
3. **When you add a new hand-edit to a generated file**, record it in
`skills/<slug>/handfixes.json` in the same change, or it will be unprotected
on the next reprint. Schema + rationale: [`docs/reprint-survival.md`](docs/reprint-survival.md).
Prefer encoding the behavior in the spec (e.g. `x-resource-id`) when the
press supports it - that survives by construction.
Find connectors whose hand-fixes are not yet recorded:
```bash
python3 tools/maintainer/check_handfixes.py --discover
```
## When a connector flips to live-verified (READ THIS TOO)
A `live-verified` badge means a real MSP confirmed the connector against a real
tenant. That is the moment it becomes worth keeping current, because it is the
first moment anyone is positioned to notice a regression. Two questions come due:
```bash
python3 tools/maintainer/check_engine_freshness.py --slug <slug>
```
1. **Is the vendored engine behind the fleet?** The bar is the newest
`printing_press_version` anywhere in `skills/*/manifest.json`, so it rises on
its own - there is no constant to maintain. Only a MINOR-version gap is
reported; a patch behind is not worth a reprint. Behind the bar means a
reprint is worth considering, subject to the hand-fix rules above. It is not
an order: a reprint is expensive and can clobber hand-fixes, and some
connectors cannot be reprinted at all (a templated `token_url` is refused by
press >= 4.30).
2. **Is the shipped binary behind `main`?** This is the one that actually bites.
A tree-only fix reaches nobody: on 2026-08-16, 41 of 61 connectors were
shipping binaries built before the 4.24 engine upgrade that had been sitting
in `main` since June. `main` being current is not evidence that any user is
- running current code - only a tag is.
+ running current code. Neither is a tag, since immutable releases landed: a tag
+ can sit in front of a stranded draft or a permanently sealed, incomplete
+ release. The evidence is a PUBLISHED release carrying its complete asset set -
+ see "What counts as released" below.
This question is answered by `release_state.classify()`, which compares a
freshly computed CLI hash against `cli_hash_at_release`. Do **not** answer it
by comparing engine version stamps: a security fix like the GO-2026-6218
toolchain sweep changes every connector's binary without touching a single
`printing_press_version`, so a stamp comparison reports the whole fleet as
fine. That is a false-GREEN, and it is exactly the failure this check exists
to catch.
`.github/workflows/live-verified.yml` runs this automatically on every badge flip
and posts the result as an issue comment. It is **advisory** - nothing gates on
it. Whoever decides should record the decision in a reply on that issue so the
next agent does not re-litigate it.
## What counts as released (READ THIS BEFORE YOU CUT OR TRUST A TAG)
A tag is necessary and no longer sufficient. This repository has GitHub
**immutable releases** enabled: a published release is sealed the instant it
exists and can never take another asset (`gh release upload` answers
`HTTP 422: Cannot upload assets to an immutable release`). So `release.yml`
assembles the entire artifact set into a **DRAFT**, asserts it is complete, and
publishes last. Three consequences an agent has to hold:
1. **A pushed tag STARTS a release; only the seal finishes one.** A failed build
target leaves a stranded DRAFT - invisible to installers, invisible to
`release_state.py`, and easily mistaken for either success or "no release
happened". The completion criterion is: published (not draft), asset set
complete.
2. **A tag push runs `release.yml` from the TAGGED commit.** Tagging a commit
that predates the draft-then-seal ordering runs that commit's publish-first
workflow, which seals an empty release and burns the version number
- permanently. Tag only a SHA on current `main`, one tag per push, and never
- `git push --tags` (it pushes stale local tags, and more than three tags in a
- single push fires no workflow run at all). Never delete a release and re-cut
- the same version - bump instead.
+ permanently. "A SHA on current `main`" is NOT the precondition and never was -
+ `main` carried the publish-first workflow for as long as it took this fix to
+ land, and it is the SHA's own `release.yml` that decides. Ask the probe, which
+ reads the workflow at that commit and names the requirement it fails:
+
+ ```bash
+ python3 tools/maintainer/check_release_pipeline.py --sha <SHA> # exit 0 = safe to tag
+ ```
+
+ Do not substitute `git show <SHA>:.github/workflows/release.yml | grep
+ -- '--draft'` for it. That grep is satisfied by any commit that merely
+ mentions the flag, and it cannot see the other four requirements (one sealing
+ job, every uploader inside its `needs:` closure, the completeness gate running
+ before the seal, the `.mcpb` inside the sealing contract). Then: one tag per
+ push, and never `git push --tags` (it pushes stale local tags, and more than
+ three tags in a single push fires no workflow run at all). Never delete a
+ release and re-cut the same version - bump instead.
3. **Only a network check is a release receipt.** `check_pinned_artifacts.py
--no-network` proves the TAG exists and nothing more, so it stays green over a
stranded draft and over a published-but-incomplete release (it was green over
`auvik-v0.1.1`, which carries 16 of its 25 assets). Use:
```bash
# per tag - the two facts that matter, then the derived asset-set check
gh release view <tag> --json isDraft,assets -q '"draft=\(.isDraft) assets=\(.assets|length)"'
gh release view <tag> --json assets \
| python3 tools/maintainer/check_release_assets.py --tag <tag> --with-mcpb
# fleet - every pinned download URL resolves to a published asset
python3 tools/maintainer/check_pinned_artifacts.py --network
# sweep for releases that never finished (expect no output)
gh api repos/Servosity/msp-skills/releases --paginate -q '.[] | select(.draft) | .tag_name'
```
A tag listed by that last sweep is a release that never finished: re-run the
Release workflow for it while its draft is still mutable.
## General
- Sign commits (`git commit -s`); no em-dashes in committed files; run
`tools/maintainer/verify_all.sh <slug>` before pushing. See `CONTRIBUTING.md`.