Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,12 @@ Architecture documentation scoped to a single package lives in that package's

## trackers/

Provider-specific host-side agent-tool contracts:
Provider-specific compact adapter profiles and host-side agent-tool contracts:

- [GitHub](trackers/github.md) — `github_graphql`
- [Linear](trackers/linear.md) — `linear_graphql`
- [GitHub Project](trackers/github-project.md) — configuration, normalization, and `github_graphql`
- [GitHub tool](trackers/github.md) — standalone `github_graphql` tool contract
- [Linear](trackers/linear.md) — configuration, normalization, and `linear_graphql`
- [File](trackers/file.md) — local/Docker E2E fixture adapter profile
Comment on lines +35 to +38

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3 — trackers/github.md just fell out of the living-documents index but is still live.

The old - [GitHub](trackers/github.md) — github_graphql entry was replaced rather than joined by the new one. The file still exists and is still referenced from two places:

  • docs/trackers/github-project.md:45 — "The host-side github_graphql tool…"
  • docs/adr/2026-08-28_agent-tool-isolation.md:217

Issue #674's acceptance criteria is registration in the docs/README.md living documents list, so leaving a still-referenced tracker doc unlisted works against the point of the change. Note the asymmetry too: linear.md keeps its tool contract inline and stays indexed, while GitHub's tool contract now lives in an unindexed sibling. Either add github.md back as its own entry, or fold it into github-project.md the way linear.md does.


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Restored docs/trackers/github.md as a separate GitHub tool entry in the living-documents index.


## designs/

Expand Down
9 changes: 5 additions & 4 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,14 @@ touches a layer, check that its slice (and the linked documents) still holds.

### 5. Integration — tracker adapters (tracker-specific code lives only here)

- GitHub Project V2: `packages/tracker-github` (including the adapter-owned linked-PR canonical-subject extension; opaque `nativeRef` data never crosses into orchestration). Source issue state and linked-PR metadata remain distinct from Project workflow status; it returns all active scoped items and derives GitHub assignment, repository-scope, pickup-label, and fork-PR eligibility as `dispatchable` with an explainable reason.
- GitHub Project V2: `packages/tracker-github` (including the adapter-owned linked-PR canonical-subject extension; opaque `nativeRef` data never crosses into orchestration). Source issue state and linked-PR metadata remain distinct from Project workflow status; candidate polling excludes terminal states and can include other non-terminal items. It derives GitHub assignment, repository-scope, pickup-label, and fork-PR eligibility as `dispatchable` with an explainable reason.
- Linear: `packages/tracker-linear`; it derives provider-native assignment eligibility as the normalized `dispatchable` contract. Its pickup labels instead filter the candidate list before dispatch, so label-ineligible Linear items are not retained for explain surfaces as `dispatchable: false` records. This adapter-side label filtering is a repository-level divergence from the upstream scheduler-owned label boundary and differs from the GitHub adapter's retained, reason-bearing records.
- File-based (E2E only): `packages/tracker-file`; fixtures may set `dispatchable` and `dispatchReason` directly to exercise the adapter-neutral scheduler gate.
- GitHub-specific planning/approval/PR-reporting extensions: `packages/extension-github-workflow`
- GitHub adapter profile: `packages/tracker-github/README.md`. Its synthetic
`Archived` state is a GitHub-specific implementation choice, not normalized
Symphony core behavior.
- Compact adapter profiles: [GitHub Project](trackers/github-project.md),
[Linear](trackers/linear.md), and [file](trackers/file.md). GitHub's
synthetic `Archived` state is a GitHub-specific implementation choice, not
normalized Symphony core behavior.
Comment on lines +92 to +95

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3 — repointing §5 away from the package README leaves two competing "adapter profile" sources.

This hunk moves the canonical pointer from packages/tracker-github/README.md to the new docs, but both package READMEs still carry their own profile prose:

  • packages/tracker-github/README.md — an ## Adapter profile section plus an "Adapter profile note:" line covering label/timestamp normalization and tracker_pagination
  • packages/tracker-linear/README.md — an "Adapter profile note:" line covering labels, timestamps, and priority 0null

They agree with the new profiles today, so nothing is wrong right now — but two independently-editable copies of the same normalization rules is exactly how the drift this epic is closing got started. Suggest reducing the package READMEs to a one-line pointer at the canonical docs/trackers/*.md profile.


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reduced both package READMEs to pointers to their canonical compact profiles, removing duplicate normalization and pagination prose.


### 6. Observability — events and status surfaces

Expand Down
49 changes: 49 additions & 0 deletions docs/trackers/file.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# File adapter profile

This compact §11.2 profile documents the `file` adapter used by local and
Docker E2E environments. It is intentionally a fixture adapter, not a
production tracker provider.

## Configuration and scope

| Item | Contract |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `tracker.kind` | `file` |
| Provider scope | A local JSON issue array selected by `tracker.settings.issuesPath`; it has no project, board, team, or remote repository selection. |
| Provider keys | `tracker.provider.path` is accepted and validated for forward compatibility but is currently inert. The effective required path is `tracker.settings.issuesPath`. Unknown provider keys are preserved by core parsing. |
| Defaults | Lifecycle defaults are `Status`, active `Ready`/`In Progress`, terminal `Done`/`Cancelled`, and no planning states. Unless explicitly configured, blocker checks use the first active state (`Ready` with these defaults). |
| Credentials | `secretEnvironmentNames()` is empty: file fixtures require no secret or environment variable. |
| Validation | A supplied `path` must be a non-empty string and fails configuration parsing with `WorkflowValidationError` when invalid. A missing runtime `issuesPath` instead fails each adapter operation with a plain `Error`. |

There is no provider request pagination, page size, timeout, or rate limit: the
adapter reads one local JSON file per operation. A missing file yields an empty
candidate list. An unreadable file, malformed JSON, or a top-level non-array is
a file/response failure rather than a silent partial read.

## Normalized issue contract

| Field or condition | File mapping |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `id` and `native_ref` | Fixture `id` is retained unchanged. `native_ref` is retained when supplied; otherwise it defaults to `{ itemId: tracker.itemId }` only for a string item ID, or `{ itemId: null }`. |
| State, labels, priority, timestamps | Valid fixture values are preserved; fixture authors supply their normalized form. The adapter does not invent provider-specific state, label, priority, or timestamp mappings. |
| `dispatchable` | An omitted value defaults to `true`; `false` and `dispatchReason` can be supplied to exercise the adapter-neutral scheduler gate. Pickup-label filtering remains available through the shared normalized filter. |
| Malformed and optional fields | Entries lacking the checked shape (`id`, `identifier`, `state`, object `repository`, object `tracker`, or a boolean `dispatchable` when supplied) are skipped with a diagnostic. This check is not exhaustive: for example, a missing `title` is currently not rejected. Optional `assigneeId` defaults to `null`. |

## Native tools and errors

The file adapter exposes no provider-native agent tool.

This table records the Symphony §11.4 target mapping required by §11.2. The
file adapter does not currently emit any structured error `category`; its
configuration parse failure is `WorkflowValidationError` and its runtime file
failures are plain errors.

| Provider-native failure | §11.4 target category | Current adapter surface |
| ---------------------------------------------- | -------------------------------------- | ------------------------------------ |
| File read failure or invalid JSON payload | `tracker_request` / `tracker_response` | Plain read/JSON error |
| No corresponding provider HTTP status | `tracker_status` | Not applicable |
| No pagination | `tracker_pagination` | Not applicable |
| No remote quota | `tracker_rate_limited` | Not applicable |
| Invalid `tracker.provider.path` | `invalid_tracker_config` | Parse-time `WorkflowValidationError` |
| Missing required `tracker.settings.issuesPath` | `invalid_tracker_config` | Per-operation plain `Error` |
| No credentials required | `missing_tracker_secret` | Not applicable |
67 changes: 67 additions & 0 deletions docs/trackers/github-project.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# GitHub Project adapter profile

This is the compact profile required by Symphony specification §11.2 for the
`github-project` tracker adapter. It describes the repository's current
Integration-layer behavior; it does not add provider semantics to core.

## Configuration and scope

| Item | Contract |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `tracker.kind` | `github-project` |
| Provider scope | `tracker.provider.project_id` selects one GitHub Project V2. `repository` is derived from each issue; the optional runtime repository filter and `--assigned-only` are adapter dispatchability rules. |
| Provider keys | `project_id`, `endpoint`, `state_field`, `priority_field`, `priority`, `pickup_labels`, `active_states`, `terminal_states`, `blocker_check_states`, and `planning_states`. Unknown provider keys are preserved by core configuration parsing. Flat `tracker.*` keys are deprecated compatibility aliases. |
| Defaults | Lifecycle defaults are `Status`, active `Todo`/`In Progress`, terminal `Done`, and no planning states. Unless explicitly configured, blocker checks use the first active state (`Todo` with these defaults). Priority is `null` unless the configured `priority` policy or deprecated `priority_field` resolves a value. |
| Credentials | `GITHUB_GRAPHQL_TOKEN` is the polling credential. `secretEnvironmentNames()` declares `GH_TOKEN`, `GH_ENTERPRISE_TOKEN`, `GITHUB_TOKEN`, and `GITHUB_GRAPHQL_TOKEN`; these names are removed from agent-child inheritance. |
| Validation | Declared string keys must be non-empty; `endpoint` must be an HTTP(S) URL; state lists must contain non-empty strings; `priority` and `pickup_labels` must be objects. Missing `project_id` or `GITHUB_GRAPHQL_TOKEN` prevents adapter use. See the error table for the §11.4 target mapping and current unnormalized surfaces. |

Candidate polling excludes configured terminal states with the GitHub `query`
argument; it can therefore return non-terminal items outside `active_states`.
State and ID refreshes are unfiltered so terminal items remain reconcilable. The
adapter uses cursor pagination with a default page size of 25; a next-page
response without a cursor is an integrity failure. Each GraphQL request has a
30-second default timeout (or the configured positive `timeoutMs`). It records
GraphQL rate-limit metadata and applies the shared GitHub policy: it honors the
later of `Retry-After` and a known primary reset. If that required wait exceeds
60 seconds, the request is not retried and the rate-limit error is surfaced.

The [2026-07-19 GitHub API rate-limit audit](../reports/2026-07-19-github-api-rate-limit-audit.md)
records why the profile keeps the page size at 25 and measures query cost by
returned `rateLimit` data rather than guessing from page count.

## Normalized issue contract

| Field or condition | GitHub Project mapping |
| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id` | GitHub Issue or Pull Request **node ID** (not the Project item ID). |
| `native_ref` | Opaque adapter data containing Project `itemId`, content type, source state, and linked-PR metadata. The Project item ID is used for Project mutations and never replaces `id`. |
| State | The configured Project status field is used. An archived Project item becomes synthetic `Archived`; this is a GitHub-specific extension, not a core state. |
| Labels and priority | Labels are trimmed, lowercased, deduplicated, and sorted. Priority is explicit Project-field or label policy output; unmapped/disabled values are `null`. |
| Timestamps | Issue and Project-item timestamps are parsed to canonical RFC 3339/ISO 8601; invalid or absent values become `null`. The newer item timestamp wins. |
| `dispatchable` | Starts `true`, then the adapter applies assignment, repository, fork-PR, pickup-label, and configured blocker rules. Ineligible items remain listed with `dispatchable: false` and an explainable `dispatchReason`. |
| Malformed and optional fields | Candidate polling silently drops non-Issue/PR Project content. Items missing the configured state are skipped with a structured event and recorded in `skippedItems`; requested ID refreshes fail instead. Optional body, labels, assignee, timestamps, priority, and linked metadata normalize to `null`, empty collections, or documented defaults rather than leaking provider shape. |

## Native tool

The host-side [`github_graphql`](github.md) tool accepts one GraphQL query or
mutation plus optional variables/operation name. It is always advertised for
repository and PR operations, but its credential and opaque `native_ref` stay
in the host process. See the linked tool document for scope and safe-use rules.

## Error-category mapping

This table records the Symphony §11.4 target mapping required by §11.2. Today,
only `tracker_pagination` is emitted as a structured adapter `category`.
The other rows describe the intended normalized category; current failures are
surfaced as `GitHubTrackerHttpError`, `GitHubTrackerQueryError`, or
Comment on lines +55 to +56

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit — this intro now under-counts the table below it by one. Non-blocking; noting it only because the round-2 fix created it.

The sentence enumerates three current surfaces as if exhaustive, but the tracker_rate_limited row you corrected in 70e8590 names a fourth — GitHubGraphQLRateLimitError — and it is the one that is partly normalized (it extends core TrackerRateLimitError), which is exactly the distinction the corrected row exists to make. A reader who stops at the intro concludes nothing here touches core contracts.

Suggested: …surfaced as GitHubTrackerHttpError, GitHubTrackerQueryError, GitHubGraphQLRateLimitError, or WorkflowValidationErrorand are not yet normalized to acategory(thoughGitHubGraphQLRateLimitErrordoes extend the coreTrackerRateLimitError).

Take it or leave it — approving either way.


Generated by Claude Code

`WorkflowValidationError` and are not yet normalized.

| Provider-native failure | §11.4 target category | Current adapter surface |
| ------------------------------------------------------ | ------------------------ | --------------------------------------------------------------------------- |
| Network/transport failure or timeout | `tracker_request` | `GitHubTrackerHttpError` |
| Non-rate-limited HTTP status | `tracker_status` | `GitHubTrackerHttpError` |
| Invalid JSON, GraphQL errors, or missing expected data | `tracker_response` | `GitHubTrackerQueryError` |
| Missing cursor or incomplete cursor traversal | `tracker_pagination` | Structured `tracker_pagination` |
| GitHub quota exhaustion or `Retry-After` response | `tracker_rate_limited` | `GitHubGraphQLRateLimitError` (core `TrackerRateLimitError`, no `category`) |
| Invalid Project/provider setting or configured shape | `invalid_tracker_config` | `WorkflowValidationError` |
| No resolved GitHub credential | `missing_tracker_secret` | Adapter initialization/configure error |
Loading
Loading