Expand description
External tracker sync: orchestration for tix export / tix import,
the Tool selector, the CommandRunner spawn seam, and the pure
mapping / conflict-resolution rules.
WP05 wires the CLI to glab (GitLab) and linear-cli (Linear) under
contracts/external-tools.md. Tokens are resolved through
token::resolve with env precedence, injected only as an env pair (never
argv), and every error/report surface scrubs the secret as <token>.
The orchestration lives here so it is testable at module granularity by
pointing PATH at fake tool scripts; export / import are pure in the
sense that they take the store, tool, config and an output sink, and the
only externally visible side effects are local issue files, tix.toml,
the tool subprocesses, and what is written to out.
§One definition of “complete”, and what this module’s job actually is
A second local completion definition in this module is a C-004 defect. There
used to be one: remote_status_is_done(status) == (status == "closed" || status == "Done"), a helper that looked like it decided whether an issue was
complete and in fact decided whether a remote state word was one of the two
this module happens to send. It had no production callers — WP02 removed the
last one — and its only remaining referrer was its own unit test, which is the
shape dead code takes when a test keeps it alive. Removed in WP07.
What this module legitimately owns is the translation: the remote trackers
have a closed set of states (opened/closed for GitLab, a workflow-state label
for Linear), so something here has to say which remote word means closed and which
local column to send it as. That is a protocol concern and it is this module’s
job.
What it does not own is the meaning of complete. That is
crate::workflow::is_completed and nothing else — so local_to_remote_status
asks the service whether a local column is complete, and
remote_to_local_status picks the column the service calls complete via
[completion_column].
The distinction matters because the two can disagree. A board whose category_map
puts completed on shipped while done_column is done has a completion
column that is not the one done_column names: comparing names exports
shipped as open and closes done instead, so the tracker and the board
disagree about the same issue in both directions.
Modules§
- beads
- Beads (
bd) interchange — the tix →bd importrow mapping, its JSONL encoding, and thebd exportreader that backs the import leg. - glab
- GitLab adapter: shells out to the
glabCLI undercontracts/external-tools.md(GitLab row). Every contract fabricate template’s command shape is reproduced; the only additions are--output json(needed to parse the create/list responses) and the token env injection. - linear
- Linear adapter: shells out to the canonical
linear-cliCLI (nesszer/linear-cli, thecargo:linear-clicrate pinned inmise.toml). - token
- Token resolution with the contract’s precedence: env var, then
tix.toml[export], then the CLI’s native auth.
Structs§
- Command
Outcome - The captured result of a spawned external tool.
- Process
Runner - Production runner:
std::process::Commandwith a synchronous output wait. - Remote
Epic - An epic reference a remote reported on one issue.
- Remote
Issue - A remote issue parsed from the tracker’s JSON list output.
- Remote
Mapping Diagnostic - The record emitted when a remote tracker cannot hold a piece of local data —
a relation (FR-005, C-005,
data-model.md§6), a parent reference, a health value, a project association (WP08, missionfirst-class-projects-01M3BYBW), or an epic reference (WP08, missionepics-work-items-01M3BYW4). - Remote
Project - A project reference a remote reported on one issue.
- Sync
Report - What one export or import run did, and what it could not do.
Enums§
- Mapped
Epic - What one local epic reference maps to on the wire.
- Mapped
Health - What one local health value maps to on the wire.
- Mapped
Initiative - What one local initiative maps to on the wire.
- Mapped
Parent - What one parent reference maps to on the wire.
- Mapped
Project - What one local project association maps to on the wire.
- Mapped
Relation - The outcome of mapping one local relationship for one tracker: either a remote representation, or a diagnostic naming why there is none.
- Tool
- The supported external tracker, selected via
--target gitlab|linear.
Traits§
- Command
Runner - Spawn seam: production uses
ProcessRunner; tests inject aFakeRunnerthat records argv/env without spawning anything. - Tracker
Adapter - Common surface both tracker adapters implement so the orchestration is tool-agnostic.
Functions§
- date_
to_ epoch_ days - Parse
YYYY-MM-DDinto days since the Unix epoch. - days_
from_ civil - The inverse of
model::civil_from_days(Howard Hinnant’sdays_from_civil): calendar date → days since the Unix epoch. - export
tix export: mirror local issues to the remote tracker, recording the remote id back into the localgitlab_id/linear_idso a re-run updates instead of creating duplicates (idempotency, FR-Remote-sync).- import
tix import: pull open remote issues into local files.- import_
with_ adapter importwith the adapter injected — theCommandRunnerpattern, so the orchestration under test is the real one and the remote side is a fake that returns cannedRemoteIssues without spawning a process (WP07 T037).- is_
remote_ newer - The conflict rule: the remote wins when its
updated_atis strictly later than the local issue’supdated(aYYYY-MM-DDinterpreted as end of day). A remote timestamp that cannot be parsed counts as not newer, so the local issue is kept. - iso_
to_ epoch_ secs - Parse an ISO-8601 remote timestamp into epoch seconds. Accepts
YYYY-MM-DDTHH:MM:SS(theTmay be a space) with optional fractional seconds and aZor±HH:MMzone. - local_
to_ remote_ status - Map a local column to the remote state/label sent to the tracker
(
contracts/external-tools.mdmapping table). - map_
epic_ from_ remote - Map one remote epic reference back onto a local reference id, or say why not.
- map_
epic_ to_ remote - Map one local epic reference to its remote representation, or say why not.
- map_
health_ to_ remote - Map one local health value to its remote representation, or say why not (T022, T023).
- map_
initiative_ to_ remote - Map one local initiative to its remote representation, or say why not (T034, T035).
- map_
parent_ to_ remote - Map one local parent reference to its remote representation, or say why not.
- map_
project_ from_ remote - Map one remote project reference back onto a local association id, or say why not.
- map_
project_ to_ remote - Map one local project association to its remote representation, or say why not.
- map_
relation_ from_ remote - The inverse: read a remote relation back to a local kind and target.
- map_
relation_ to_ remote - Map one local relationship to its remote representation, or say why not.
- remote_
epics - Parse the epic references one remote issue reported.
- remote_
projects - Parse the project references one remote issue reported.
- remote_
to_ local_ status - Map a remote state/label back onto a local column. Closed/Done maps to the effective done column; anything else lands in the column whose name matches (case-insensitive), else the first column — per the contract’s “whose name matches, else into the first column”.
- scrub_
secret - Redact a resolved secret from text flowing through error/report surfaces.