From 80249350cca218900b857f8d236fda5c1e60e55b Mon Sep 17 00:00:00 2001 From: mrhid6 Date: Thu, 10 Sep 2026 09:18:56 +0000 Subject: [PATCH] chore: replace em dashes with hyphens, add no-em-dash rule to CLAUDE.md --- .gitea/workflows/release.yml | 8 ++++---- CLAUDE.md | 34 +++++++++++++++++++--------------- Dockerfile | 2 +- internal/cmd/inspect.go | 2 +- internal/cmd/restore.go | 4 ++-- internal/cmd/verify.go | 4 ++-- 6 files changed, 29 insertions(+), 25 deletions(-) diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 4a26ea5..5faf2b0 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -4,8 +4,8 @@ on: push: # A bare v* now this is its own repository. The old prefixed # vantagectl/v* tag existed to namespace one component's releases - # inside a shared repository, and unlike the agent's agent/v* — which - # the control plane greps release tag names for — nothing reads this + # inside a shared repository, and unlike the agent's agent/v* - which + # the control plane greps release tag names for - nothing reads this # one programmatically. Releases before the move keep their prefixed # tags in the monorepo's release list. tags: @@ -80,7 +80,7 @@ jobs: # ran; ":latest, rebuilt whenever main moved" cannot be named after the # fact. It is a separate job from the binaries because it needs a # docker-capable runner rather than a Go one, and it does not need the - # binaries — the image builds from source in its own stage. + # binaries - the image builds from source in its own stage. image: runs-on: ubuntu-docker container: docker:dind @@ -102,7 +102,7 @@ jobs: echo "IMAGE_TAG=${VERSION#v}" >> $GITHUB_OUTPUT # The image build resolves the private vantage-shared module, so - # it needs a credential of its own — this job does not share the + # it needs a credential of its own - this job does not share the # build job's filesystem. - name: Write the module fetch credential run: | diff --git a/CLAUDE.md b/CLAUDE.md index 38b0690..86dd5a7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,7 @@ The backup and restore CLI for a Vantage control plane. Extracted from the `vantage` monorepo with its history; the command is the repository root, so the module is `gitea.hostxtra.co.uk/vantage/vantage-ctl`. -**The command is still `vantagectl`.** Only the repository is `vantage-ctl` — +**The command is still `vantagectl`.** Only the repository is `vantage-ctl` - the binary, the cobra `Use:` string and every runbook keep the name an operator actually types. @@ -30,7 +30,7 @@ failure is the reason you reached for it. Almost none of it is here. Dump, restore, verify, manifest and fingerprint are `shared/backup` in **`vantage-shared`**; `internal/cmd` holds argument parsing and the words an operator reads. That split is what would let the control plane -import `shared/backup` later — a scheduled in-process backup, say — without a +import `shared/backup` later - a scheduled in-process backup, say - without a second implementation to keep in sync. `shared/cryptobox` is the same move one layer down: the single AES-256-GCM implementation, which the server delegates to rather than keeping its own copy that `backup` would have had to duplicate to @@ -38,8 +38,8 @@ decrypt a probe value during `verify`. So **a fix to backup behaviour is usually a `vantage-shared` release plus a pin bump here**, and it is not live until this repository cuts a tag. That is -deliberate — an operator restoring a database should be running a version they -can name — but it does mean the fix is not in anybody's hands the moment it +deliberate - an operator restoring a database should be running a version they +can name - but it does mean the fix is not in anybody's hands the moment it merges. ## The refusals, and why each exists @@ -50,28 +50,28 @@ each one guards something with no way back. - **`backup` will not run without `KEY_ENCRYPTION_KEY`** unless `--allow-no-key` is passed. The archive stores a SHA-256 **fingerprint** of the key, never the key. An archive with no fingerprint cannot tell a later restore that the wrong - key is in hand — it can only find that out when the data comes back as noise. + key is in hand - it can only find that out when the data comes back as noise. - **`restore` refuses a non-empty target database, and there are no merge semantics.** No code path upserts an archive's documents over existing ones: merging two control planes reconciles nothing about which SSH keys are still valid or which users still exist, and an upsert would resurrect a revoked key or a deleted member from the older side. - **`--force` needs a second assurance**: `--confirm-db NAME` matching the - target exactly, which works everywhere, or — on a terminal only, and only when - `--confirm-db` was not given — the database name typed back at a prompt. + target exactly, which works everywhere, or - on a terminal only, and only when + `--confirm-db` was not given - the database name typed back at a prompt. `--confirm-db` is accepted on a terminal too, and is the stronger of the two: naming the target in the command itself means a copied command carries its intended target and cannot destroy a different one by accident. Without a terminal and without `--confirm-db`, `--force` is refused. - **`--force` drops only what the archive names.** Collections in the target - that the archive does not carry are left alone and **named in a warning** — an + that the archive does not carry are left alone and **named in a warning** - an archive taken with `--exclude workflow_log_lines`, restored over a live database, leaves the old lines joined to restored runs and the operator must be told. Dropping them instead would delete data nobody asked to delete. ## Three decisions that look like details and are not -- **Collections are enumerated live**, not read from a static list — the +- **Collections are enumerated live**, not read from a static list - the opposite of what instance-deletion purge does in the control plane. Purge must never miss a tenant-scoped collection, so it keeps a hand-maintained registry; a backup must never miss **any** collection, including the ones carrying no @@ -92,7 +92,7 @@ each one guards something with no way back. ## The mirror that fails silently **`backup.ciphertextFields` lives in `vantage-shared` and mirrors -`server/internal/models` in the `vantage` repository by hand** — `shared/` cannot +`server/internal/models` in the `vantage` repository by hand** - `shared/` cannot import it. The map naming each collection's `*_enc` fields (`keys`, `secrets`, `auth_providers`, `console_sessions`) must change in the same commit as any of those bson tags. @@ -100,21 +100,21 @@ those bson tags. Wrong field names are **silent**: `verify`'s live probe simply finds no ciphertext and reports "this database stores no ciphertext yet", so the one gate that catches what a key fingerprint cannot becomes a no-op. `settings` is -deliberately in neither that map nor `CiphertextCollections()` — its ESO read +deliberately in neither that map nor `CiphertextCollections()` - its ESO read token is a SHA-256 hash, not ciphertext. ## Building and releasing `GOPRIVATE=gitea.hostxtra.co.uk/*` plus a credential, since `vantage-shared` is private. CI writes a netrc from `REGISTRY_USER` + `RELEASE_TOKEN` in **both** -jobs — they do not share a filesystem — and the image build takes it as a +jobs - they do not share a filesystem - and the image build takes it as a BuildKit secret rather than a build arg, which would survive in the builder layer's history. **`RELEASE_TOKEN` needs read access to the `vantage` org.** Tags are a bare **`v*`** now this is its own repository. The old `vantagectl/v*` prefix namespaced one component's releases inside a shared -repository; unlike the agent's `agent/v*` — which the control plane greps -release tag names for — nothing reads this one programmatically. Releases from +repository; unlike the agent's `agent/v*` - which the control plane greps +release tag names for - nothing reads this one programmatically. Releases from before the move keep their prefixed tags in the monorepo's release list. ```bash @@ -128,5 +128,9 @@ Builds `linux/amd64`, `linux/arm64`, `darwin/arm64` and `windows/amd64`, writes **The runtime stage is `scratch`, and it carries an explicitly copied `/tmp`.** `restore` extracts an archive to a temporary directory before verifying its checksums, and a scratch image has none for `os.MkdirTemp` to find. Without it -every restore fails — the same omission costs the control plane only its +every restore fails - the same omission costs the control plane only its vulnerability scanning, but here it is the whole tool. + +## Writing style + +Never use em dashes (the long dash character) anywhere: code, comments, UI copy, docs, commit messages. Use a plain hyphen ` - `, a comma, a colon, or split the sentence instead. diff --git a/Dockerfile b/Dockerfile index 80fbb65..841685d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,7 +27,7 @@ RUN --mount=type=secret,id=netrc,target=/root/.netrc \ -ldflags="-s -w -X main.Version=${VERSION}" -o /vantagectl . # Staged so the scratch image below can have a /tmp. It cannot mkdir one -# itself — scratch has no shell. +# itself - scratch has no shell. RUN mkdir -p /staging/tmp && chmod 1777 /staging/tmp # Runtime stage diff --git a/internal/cmd/inspect.go b/internal/cmd/inspect.go index 29f2284..1458452 100644 --- a/internal/cmd/inspect.go +++ b/internal/cmd/inspect.go @@ -39,7 +39,7 @@ func renderManifest(w io.Writer, m backup.Manifest) { fmt.Fprintf(w, "Format version %d\n", m.FormatVersion) if m.KeyFingerprint == nil { - fmt.Fprintf(w, "Key none recorded — this archive cannot be checked "+ + fmt.Fprintf(w, "Key none recorded - this archive cannot be checked "+ "against any KEY_ENCRYPTION_KEY\n") } else { fmt.Fprintf(w, "Key %s\n", *m.KeyFingerprint) diff --git a/internal/cmd/restore.go b/internal/cmd/restore.go index b42a718..c97cb42 100644 --- a/internal/cmd/restore.go +++ b/internal/cmd/restore.go @@ -102,8 +102,8 @@ func newRestoreCmd() *cobra.Command { // confirmDestruction gates a --force restore. // -// On a terminal the operator types the database name. Without one — a -// Kubernetes Job, a CI step, a cron entry — the same assurance comes from +// On a terminal the operator types the database name. Without one - a +// Kubernetes Job, a CI step, a cron entry - the same assurance comes from // --confirm-db, whose value must equal the target. Naming the database in the // argument means a copy-pasted command carries its intended target with it and // cannot destroy a different one. diff --git a/internal/cmd/verify.go b/internal/cmd/verify.go index ac2017e..6a6d541 100644 --- a/internal/cmd/verify.go +++ b/internal/cmd/verify.go @@ -31,8 +31,8 @@ func newVerifyCmd() *cobra.Command { } defer archive.Close() - // The key is read unconditionally. Archive-only mode — no MONGO_URI, - // which is what a scheduled check uses — must still compare the key + // The key is read unconditionally. Archive-only mode - no MONGO_URI, + // which is what a scheduled check uses - must still compare the key // in hand against the archive's fingerprint; leaving it unset there // reported "KEY_ENCRYPTION_KEY is not set" for a key that was set // and correct.