# AGENTS.md — internal/mapping (BRIDGE) **md ↔ Gitea's payloads. The whole translation, and only the translation.** Pure functions: no network, no filesystem, no flags, no clock. Give it a payload and it hands back a domain issue; give it an issue and it hands back a request body. That purity is the point — it can be reasoned about and tested without a Gitea anywhere, and it is the one package to open when the two representations disagree. Imports [`issue`](../issue/AGENTS.md), [`wire`](../wire/AGENTS.md) and the SDK. Nothing imports it but [`cmd`](../cmd/AGENTS.md) — not the domain, not the transport. Both sides speak the SDK's shapes, which is what lets the two meet without either reaching into the other. | file | what is in it | |---|---| | `mapping.go` | the sync-owned metadata keys (`gitea`, `url`, `synced`, `remote-updated`, `comments`, `branch`), `Origin`, `ApplyRemote` | | `frompayload.go` | `FromPayload` and the accessors around it: `LabelNames`, `AssigneeLogins`, `MergeCheckboxState` | | `torequest.go` | the create/edit request bodies, `LabelIDsFor` | | `labels.go` | `LabelColor`, `LabelSpecs`, `CanonicalLabelSpecs`, `DefaultColor` | | `marker.go` | `IDMarker`, `IDInBody`, and the strip on the way in | | `layering_test.go` | the two tests that keep this package pure | ## What crosses the boundary, and what does not ``` domain Gitea note ---------------------------------------------------------------------- id (slug) body marker , first line of the tracker-side body; stripped out of the local copy — see marker.go title title verbatim, both ways body body verbatim up, verbatim down except the marker and checkbox state state state open/closed, the same vocabulary labels labels[] names both ways; ids only on write assignees assignees[] logins milestone milestone.title resolved to an id on write depends — slugs; #N is translated at this edge — number, html_url lands in Extra as gitea:/url: — ref Extra as branch:; push fills it from git ``` Only the **login** of a Gitea user crosses: it is what `assignees:` holds, and a display name is not an identity anything can be pushed against. Label and assignee lists are appended into a nil slice, so an issue with no labels is the same value as one loaded from a file — two spellings of "none" is a comparison bug waiting to happen. `depends:` is the authoritative graph and is always slugs. The body's `## Depends on` prose is passed through **unchanged in both directions**: a pull seeds `depends:` from the `#N` it finds there, and a push never rewrites what the author wrote. Deliberate — a translator that edits prose churns the body on every round trip. ## The id marker The **one** thing this package adds to a body, and it adds it because the slug has to survive a push: push deletes the local file, so the tracker has to be the thing that remembers what the issue was called here. - one place formats it (`IDMarker`), one regex reads it — and the regex accepts more than the formatter writes, including the older ``, because issues pushed before the rename are still in the tracker; - the **first** valid marker wins; a second is ignored and removed on the way in; - the captured text must be a slug by the domain's own rule, so a mangled comment falls back to the title instead of naming a file after garbage; - it is stripped before anything else looks at the body, so checkboxes, `#N` references and what lands on disk all see the body the author wrote. ## The checkbox merge `MergeCheckboxState` is the one exception to "a pull overwrites the body", and deliberately the narrowest one that works. **A tick is monotone** — an item only travels `[ ]` → `[x]` — so the two sides are joined by a **set union**: no base version, no drift tracking, no conflict to resolve. An item comes out ticked when either side has it ticked; everything else in the body is still the remote's word. Matching is on `Checkbox.Text`, which the domain parser has already stripped and rejoined with single spaces, so rewrapping a long item does not cost it its tick. It is otherwise literal: reword an item and it is a different item. The same text more than once is read as the rule says, as a set — one ticked local item ticks every remote item with that text. Pairing duplicates up by order is the reading that can still drop a tick, and dropping a tick is the bug this exists to fix. ## Labels, and the two write paths Colours live here, not in the domain: a hex code is how a tracker paints a chip and not what an issue is. `CanonicalLabelSpecs` is derived from the domain's own list rather than restated, so adding a type over in the taxonomy creates it on the next bootstrap with no line changing here but the colour. `DefaultColor` paints everything outside the canonical set, because `tech/*` and `comp/*` are project-specific and guessing a colour for one invents a meaning it does not have. `LabelIDsFor` is exported so that a create and a repair cannot derive the answer differently: **Gitea's edit endpoint carries no labels**, so an issue that already exists gets its label set through a `PUT`, and a `PUT` that disagreed with what a create would have sent would make a pushed issue and a re-pushed one two different things. `nil` means "resolved no ids"; an **empty, non-nil** list means "resolved some and matched none", which is a statement to the tracker — `[]` clears every label on the issue. ## Purity, and the one weakening `layering_test.go` checks **direct** imports and fails on `os`, `net/http`, `os/exec`, `internal/gitea`, `internal/config` and `internal/project`; a second test greps the sources for `time.Now`. It does not walk the dependency closure, and it cannot: the SDK's types come with the SDK's client attached, so the graph contains an HTTP client whatever this package does with it. The full reasoning — and why `time` is allowed where it once was not — is in [`internal/AGENTS.md`](../AGENTS.md). ## Keeping this file true - **Scope:** every `.go` file here — the field table, the marker, the merge, the colours. - **Update it when** a field starts or stops crossing the boundary (the table is the contract), a sync-owned metadata key is added, the marker spelling changes or an older one stops being read, or the purity test is loosened. - **Do not** put a request here that anything else could make. This package returns values; [`gitea`](../gitea/AGENTS.md) sends them.