01fb5a2703
There is no CI: the instance has no act_runner and none is planned, so releases are cut by hand. That makes `make check` the only thing standing between a mistake and the tracker, and it is one command: gofmt, vet, the suite with the cache defeated, `go mod verify`, a vendored build, and `kettle gen skills --check`. The last one is the invariant worth having — the plugin's SKILL.md command reference is generated from the binary's registry, so a flag that changed cannot ship with documentation that recommends the old one. `cli/cmd/release` publishes to Gitea using the same SDK the binary already vendors, which is a pleasing thing to be able to say: nothing third-party handles the artifacts. It is a second binary rather than a `kettle` subcommand on purpose — `kettle`'s command tree is what generates the plugin's skills, so a verb there ships to every operator, and publishing a release is build infrastructure. It is idempotent end to end: an existing release for the tag is reused, an asset of the same name is replaced rather than doubled, and a retried run converges instead of duplicating. `make release` refuses three things, each with its own message: a dirty working tree, a TAG that is not what `git describe` reports, and a tag the remote does not have. A release built from uncommitted code is unreproducible and nobody finds out until they need to reproduce it. `kettle version` reports the stamp, the toolchain and the VCS revision. The default is `dev`, and a hand build says so and means it — a binary out of somebody's working tree is not a release and must not claim to be one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
102 lines
4.9 KiB
Markdown
102 lines
4.9 KiB
Markdown
# AGENTS.md — internal/config
|
|
|
|
**Two files: what this project is, and who this machine is.** The only package in
|
|
the tree that imports yaml.
|
|
|
|
| file | what is in it |
|
|
|---|---|
|
|
| `config.go` | `Project` and `Logins` (the two files), `Resolve`/`ResolveOutsideAProject`/`Require`, `Resolved` with `Complete` and `Redacted`, the `KETTLE_*` overrides, and the 0600 write |
|
|
|
|
## The split is the whole design
|
|
|
|
```
|
|
<project>/.kettle/config.yaml login: noodles a NAME, never a token
|
|
repo: owner/name
|
|
|
|
~/.config/kettle/logins.yaml logins: [{name, url, user, token}] mode 0600
|
|
```
|
|
|
|
**A token in a file inside a working tree ends up in a commit.** Not always, not
|
|
immediately, and not by anyone careless — but a project config is exactly the file
|
|
somebody eventually decides to share, and a secret that has ever been pushed has to
|
|
be rotated. So the project pins a login by **name**, and the name is worth nothing
|
|
on its own, which is what makes it safe to keep in a repository.
|
|
|
|
Which tokens this computer holds is a fact about the computer, the way which issues
|
|
a tree holds is a fact about the tree. `SaveLogins` writes 0600 into a 0700
|
|
directory; nothing else on the machine has any business reading it. `$KETTLE_CONFIG_HOME`
|
|
relocates it — the test suite sets it, so a run can neither read nor overwrite the
|
|
developer's own tokens — and `$XDG_CONFIG_HOME` is honoured too.
|
|
|
|
**Nothing prints a token.** `Redacted` is what a receipt gets; `kettle config` shows
|
|
`(set)`.
|
|
|
|
## Resolution, and why it fails early
|
|
|
|
`Resolve` merges three sources — the project config, the machine's login file, and
|
|
the environment — into `Resolved`, which is everything the transport needs.
|
|
|
|
**Every failure names the file it read and the command that fixes it.**
|
|
"401 Unauthorized" is what happens when this function is allowed to return a
|
|
half-filled struct, and a 401 names nothing an operator can act on.
|
|
|
|
The same discipline splits the two "missing" answers: a missing `config.yaml` is
|
|
`ErrNoConfig`, not an empty config, because "this project has not been told which
|
|
tracker it belongs to" and "it belongs to no tracker" are different answers and only
|
|
one is fixed by running `init`. A missing login file, by contrast, **is** an empty
|
|
list — a machine with no logins yet is an ordinary machine.
|
|
|
|
`Complete` is that assertion on its own, as a method, because the two questions are
|
|
different: `kettle config` wants to **show** a half-filled configuration and
|
|
everything that dials wants to **refuse** one. `Require` is `Resolve` plus
|
|
`Complete`; [`gitea.New`](../gitea/AGENTS.md) and `cmd/release` call `Complete`
|
|
themselves, so a client can never be built from a struct that is missing a field.
|
|
|
|
`ResolveOutsideAProject` is for the one caller that legitimately stands nowhere near
|
|
a project: [`cmd/release`](../../cmd/release/AGENTS.md), run from a fresh clone. The
|
|
marker is gitignored, so a clone has none and a build tool must not create one — and
|
|
with no marker there is nothing to merge, so the **environment is** the
|
|
configuration. A marker that is there is read as always, so the same command run
|
|
from a maintainer's own checkout picks up the login pinned in it. Every other caller
|
|
wants `Resolve`, where "no project" is the answer rather than a state to work
|
|
around: a push that quietly ran against whatever was in the environment would be a
|
|
push into somebody else's repository.
|
|
|
|
`ReadProjectFile` exists for exactly one caller: `kettle init`, which is creating
|
|
the marker `LoadProject` walks for, and on a dry run may not have created it at all.
|
|
|
|
## Overrides
|
|
|
|
| variable | shadows |
|
|
|---|---|
|
|
| `KETTLE_LOGIN` | `login:` in the project config |
|
|
| `KETTLE_REPO` | `repo:` in the project config |
|
|
| `KETTLE_URL` | the login's `url` |
|
|
| `KETTLE_TOKEN` | the login's `token` |
|
|
| `KETTLE_CONFIG_HOME` | the directory holding `logins.yaml` |
|
|
|
|
Each wins over the file it shadows. They exist for CI, for a one-off run against
|
|
another instance, and for anyone who would rather not have a token on disk at all.
|
|
|
|
## Unknown keys are an error
|
|
|
|
Not a silent drop. An older binary reading a newer config would otherwise delete the
|
|
setting it did not recognize the next time it wrote the file — which is a data-loss
|
|
bug that only shows up on the machine running the older build.
|
|
|
|
## What does not belong here
|
|
|
|
A request, a store path, an issue. This package reads and writes two files and
|
|
answers "who am I and where am I pointed"; [`gitea`](../gitea/AGENTS.md) takes the
|
|
answer and dials, and the paths themselves come from
|
|
[`project`](../project/AGENTS.md).
|
|
|
|
## Keeping this file true
|
|
|
|
- **Scope:** `config.go` — the two files, their fields, the overrides, the file modes.
|
|
- **Update it when** a field is added to either file (both tables above are the
|
|
contract), an override is added or renamed, the location or mode of the login file
|
|
changes, or the unknown-key policy changes.
|
|
- **Do not** move a credential into the project file, and if that ever changes, the
|
|
argument above is what has to be answered first.
|