Skip to content
Open
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
57 changes: 31 additions & 26 deletions AGENTS.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Designed for large monorepos and fast-moving teams where concurrent changes can

## Repository layout

Cross-domain Go code (errors, metrics, consumer framework, HTTP helpers, shared entities, shared extension contracts) lives under [`platform/`](platform/README.md). Each product domain has its own tree (`submitqueue/`, `stovepipe/`, …) and grows into `gateway/`, `orchestrator/`, `entity/`, `extension/`, and domain-local `core/` — though a domain may start smaller (Stovepipe is currently a single Ping-only service with just `controller/`). See [AGENTS.md](AGENTS.md) for conventions and import paths.
Cross-domain Go code (errors, metrics, consumer framework, HTTP helpers, shared entities, shared extension contracts) lives under [`platform/`](platform/README.md). Each product domain has its own tree (`submitqueue/`, `stovepipe/`, …). Multi-service domains may split into `gateway/` and `orchestrator/`; single-service domains keep controllers directly under the domain root. Stovepipe currently exposes ingestion behavior and runs its own queue pipeline. See [AGENTS.md](AGENTS.md) for conventions and import paths.

## Quick Start

Expand Down
10 changes: 5 additions & 5 deletions submitqueue/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

SubmitQueue service layout:

- `gateway/` — Gateway service: entry point for land requests (`Ping`, `Land`, `Cancel` RPCs).
- `orchestrator/` — Orchestrator service: coordinates the land pipeline (batch, speculate, build, merge, conclude, ...).
- `extension/` — SubmitQueue-specific extension implementations (storage, counter, changestore, mergechecker, pusher, scorer, conflict, queueconfig, buildrunner, ...).
- `gateway/` — Gateway service: entry point for `Ping`, `Land`, `Cancel`, request-summary, request-history, and queue-listing RPCs. It also consumes request-log events and maintains the public request projections.
- `orchestrator/` — Orchestrator service: coordinates validation, dependency analysis, speculation, builds, landing, cancellation, conclusion, hooks, and DLQ reconciliation.
- `extension/` — SubmitQueue-specific extension contracts and implementations, including storage, queue configuration, change providers, validation, conflict analysis, speculation, and build runners.
- `entity/` — SubmitQueue-specific domain entities.
- `core/` — Infrastructure shared across SubmitQueue's own services (gateway and orchestrator): the queue `consumer` framework, the `request` lifecycle, and topic keys. The SubmitQueue-scoped analogue of the repo-level `platform/`.
- `core/` — Infrastructure shared across SubmitQueue's own services, including request and batch lifecycle helpers, change-set resolution, and internal topic keys.

Cross-domain building blocks live outside this directory: shared entities in `platform/base/`, shared extensions in `platform/extension/`, and cross-domain infrastructure in `platform/`.
Cross-domain building blocks live outside this directory: shared entities in `platform/base/`, shared extensions in `platform/extension/`, and cross-domain infrastructure such as the consumer framework in `platform/`.
10 changes: 5 additions & 5 deletions submitqueue/core/changeset/README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
# changeset

`changeset` resolves batch identity into the changes a batch contains. It is the single place the orchestrator walks batch → requests → changes, consolidating the resolution the build and merge controllers each performed privately.
`changeset` resolves batch identity into the changes a batch contains. Current consumers include build-runner and scorer implementations and the path-overlap conflict analyzer. The merge controller loads member requests directly because its Runway payload preserves one ordered merge step per request.

## Why it exists

A `Batch` is a thin reference entity: it carries the IDs of the requests it contains, not their changes. Decision and action extensions (the scorer, build runner, pusher, and future detail-aware conflict analyzers) are handed that identity and resolve the granular content themselves through an injected `Resolver`, rather than depending on a controller to pre-resolve and pass the data in. The resolver depends only on the two resolution-target stores — the request store (to walk a batch's contained requests) and the change store (to attach provider details) — and nothing else.
A `Batch` is a thin reference entity: it carries the IDs of the requests it contains, not their changes. Decision and action extensions such as scorers, build runners, and conflict analyzers are handed that identity and resolve the granular content themselves through an injected `Resolver`, rather than depending on a controller to pre-resolve and pass the data in. The resolver uses the queue-scoped storage aggregate to reach the request store (to walk a batch's contained requests) and the change store (to attach provider details).

## Two fidelities

The resolver offers the same walk at two levels of detail, and both preserve batch boundaries — neither flattens across batches, so a caller that wants a flat list flattens the result itself:
Both methods operate on one batch per call:

- The raw view returns each batch's contained changes as URIs only, one group per input batch, in input order. It performs no change-store read. The build stage uses it for base and head inputs; the merge stage uses it for the pusher.
- The detailed view returns a single batch's normalized, batch-level changes: one entry per claimed URI, each carrying the provider details recorded in the change store, aggregated across every request in the batch. Because the change store returns rows for every request that ever claimed a URI, the resolver selects the row owned by the requesting request. The scorer uses it, as will any analyzer that needs changed-file or line-count facts.
- The raw view returns a batch's contained changes as URIs only, in request order. It performs no change-store read. Build-runner implementations use it to construct base and head inputs.
- The detailed view returns a single batch's normalized, batch-level changes: one entry per claimed URI, each carrying the provider details recorded in the change store, aggregated across every request in the batch. Because the change store returns rows for every request that ever claimed a URI, the resolver selects the row owned by the requesting request. Scorers and detail-aware conflict analyzers use this view.

## Testing

Expand Down
43 changes: 11 additions & 32 deletions submitqueue/extension/conflict/README.md
Original file line number Diff line number Diff line change
@@ -1,46 +1,25 @@
# Conflict

Vendor-agnostic interface for detecting conflicts between a candidate batch
and the batches already in flight.
Vendor-agnostic interface for detecting conflicts between a candidate batch and the batches already in flight.

## Interface

`Analyzer` exposes a single `Analyze` method that takes the candidate batch
and the list of in-flight batches it might conflict with. It returns the
subset of in-flight batches that conflict with the candidate, each paired
with a `ConflictType` describing the kind of conflict. An empty result means
the candidate is free to advance independently.
`Analyzer` exposes a single `Analyze` method that takes the candidate batch and the list of in-flight batches it might conflict with. It returns the subset of in-flight batches that conflict with the candidate, each paired with a `ConflictType` describing the kind of conflict. An empty result means the candidate is free to advance independently.

Callers are responsible for filtering out the candidate itself and any
terminal batches from the in-flight list before invoking the analyzer. The
analyzer itself stays free of lifecycle knowledge. A non-nil error reports
an infrastructure failure of the analysis and should be treated as
retryable by the caller.
Callers are responsible for filtering out the candidate itself and any terminal batches from the in-flight list before invoking the analyzer. The analyzer itself stays free of lifecycle-transition knowledge. A non-nil error reports that analysis could not be completed; implementations return plain errors and the configured error classifiers decide retryability.

The analyzer is intentionally pure with respect to batch state: it does not
mutate inputs, does not read storage, and may be called concurrently. Real
implementations are expected to resolve the batch contents (e.g. changed
build targets, modified files) via whichever upstream system they depend
on, and to return as much classification detail as that system supports.
The analyzer does not mutate batch inputs and may be called concurrently. Implementations resolve the batch contents they need through injected dependencies. For example, `pathoverlap` uses a `changeset.Resolver`, whose store-backed implementation reads queue-scoped request and change records.

## Implementations

- [`all/`](all/) — pessimistic stub: reports every in-flight batch as a
`ConflictTypeConservative` conflict. Useful as a worst-case baseline and
for wiring tests where speculation must serialize.
- [`none/`](none/) — optimistic stub: reports no conflicts. Useful as a
best-case baseline and for wiring tests where speculation should run all
batches in parallel.
- [`all/`](all/) — pessimistic stub: reports every in-flight batch as a `ConflictTypeConservative` conflict. Useful as a worst-case baseline and for wiring tests where speculation must serialize.
- [`fake/`](fake/) — wraps another analyzer and optionally injects configured failures for tests and example wiring.
- [`none/`](none/) — optimistic stub: reports no conflicts. Useful as a best-case baseline and for wiring tests where speculation should run all batches in parallel.
- [`pathoverlap/`](pathoverlap/) — resolves changed files and reports overlap conflicts by whole file or parent directory.

## Adding a new backend

1. Create `extension/conflict/{backend}/` with an `Analyzer` implementation.
2. Resolve each `entity.Batch` into whatever signal the backend needs
(e.g. changed build targets, files touched, dependency graphs).
3. Emit one `Conflict` per (in-flight batch, detected conflict type). Pick
the most specific `ConflictType` your backend can determine; use
`ConflictTypeConservative` only when the backend cannot prove the absence
of a conflict and falls back to a pessimistic default. Introduce a new
`ConflictType` constant when you can classify the conflict more precisely.
4. Return a plain error for transient infrastructure failures so callers
can classify and retry.
2. Resolve each `entity.Batch` into whatever signal the backend needs (e.g. changed build targets, files touched, dependency graphs).
3. Emit one `Conflict` per (in-flight batch, detected conflict type). Pick the most specific `ConflictType` your backend can determine; use `ConflictTypeConservative` only when the backend cannot prove the absence of a conflict and falls back to a pessimistic default. Introduce a new `ConflictType` constant when you can classify the conflict more precisely.
4. Return plain errors and let the consumer's configured classifiers determine retry behavior.
6 changes: 2 additions & 4 deletions submitqueue/extension/storage/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ Pluggable persistence interfaces for SubmitQueue entities (requests, batches, de

Storage follows the extension contract: the queue-scoped store aggregate is resolved per queue through a factory keyed by queue name, mirroring how every decision/action extension resolves its implementation. A resolved aggregate is bound to its queue — entity arguments whose queue disagrees with the binding are rejected, queue-keyed reads are implicitly scoped, and the host wiring decides which backend serves which queue (single shared backend by default).

Three read-model stores are deliberately global rather than queue-scoped, because their lookups start from identifiers that arrive without queue context (a bare request ID or change URI at the status API): the request log, the request summary, and the change-URI mapping. They are injected individually as standalone seams, following the gateway's per-store injection. The queue registry (`queueconfig`) was never part of this aggregate and stays the registry the factory sits beside.

The classification rule: a store is queue-scoped when every read path authoritatively holds the queue before the first read, and global when any read path begins from an identifier that arrives without queue context. Entity IDs are opaque — no reader may derive the queue from an ID prefix; the queue travels explicitly on payloads and requests.
Every entity and read-model store is a member of the queue-scoped `Storage` aggregate returned by `Factory.For`. Gateway read requests therefore carry the queue explicitly before resolving request summaries, logs, URI mappings, or queue-list projections. Entity IDs remain opaque — readers do not derive the queue from an ID prefix.

## Optimistic locking contract

Expand Down Expand Up @@ -48,7 +46,7 @@ A `Get` immediately following a successful write (`Create`/`Update`) — by the

## Key-value contract

Store interfaces are designed for the storage technology *space*, not for SQL (see the Extensions section of the repo [AGENTS.md](../../../../AGENTS.md)): every method must be satisfiable by a plain key-value backend (DynamoDB, Bigtable, an in-memory map) as cheaply as by MySQL. Concretely, a store exposes only get/put/conditional-update **by primary key**. No lookups by other attributes, no listings filtered server-side, no joins.
Store interfaces are designed for the storage technology *space*, not for SQL (see the Extensions section of the repo [AGENTS.md](../../../AGENTS.md)): every method must be satisfiable by a plain key-value backend (DynamoDB, Bigtable, an in-memory map) as cheaply as by MySQL. Contracts use point operations by complete primary key and deliberate bounded prefix/range reads where the lookup fields are leading components of the primary key. They do not require secondary-index lookups, joins, or arbitrary server-side predicates.

**The smell test is the index.** If implementing a proposed store method in MySQL requires adding a secondary index (`KEY idx_*`) to the schema, the method is a query-by-attribute in disguise and the contract has left the key-value space — a KV backend would need a global secondary index or a hand-maintained index table to fake it. Treat a new `KEY` line in a schema diff as a design review flag, not a tuning detail.

Expand Down
16 changes: 9 additions & 7 deletions submitqueue/gateway/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,23 @@ The gateway is the RPC entry point to SubmitQueue. It accepts `Land`, `Cancel`,

## Request receipts and current summaries

`Land` creates gateway-owned receipt projections before publishing the request:
`Land` first creates an internal request summary in `accepting` state. That receipt prevents a failed publish from exposing work that was never admitted. After the start message is published, the gateway records the `accepted` log through the request materializer.

- An authoritative request summary keyed by sqid.
- One exact change-URI mapping per submitted URI.
- A queue-ordered receipt projection used by `List`.
The materializer appends the log, chooses the winning current status, and activates or repairs the public projections:

`GetRequestSummaryByID` and `GetRequestSummaryByChangeURI` read authoritative summaries. `List` reads the queue projection and may briefly lag those summaries while eventual repair converges.
- The authoritative request summary keyed by sqid.
- One change-URI mapping per submitted URI.
- The queue-ordered projection used by `List`.

`GetRequestSummaryByID` and `GetRequestSummaryByChangeURI` read authoritative summaries. `List` reads the queue projection and may briefly lag while later log materialization repairs a partial attempt. If recording `accepted` fails after publication, `Land` still succeeds because subsequent pipeline logs can advance the internal `accepting` summary and create the public projections.

## Request log ownership

The gateway owns the request log read model and is the only service that reads it.

- For statuses produced synchronously by the gateway, such as `accepted` on `Land` and `cancelling` on `Cancel`, the gateway persists the event through the shared request-log materializer before returning or publishing.
- `Land` publishes first and then attempts to materialize `accepted`; publication is its success boundary. `Cancel` materializes `cancelling` before publishing so the user's intent is visible when the RPC returns.
- For statuses produced downstream, the orchestrator publishes entries to the `log` topic through `submitqueue/core/request.PublishLog`. The gateway consumes that topic and persists each entry through the same materializer.
- Orchestrator DLQ reconciliation materializes terminal repairs directly so the DLQ delivery remains unacknowledged until the log and public projections converge.
- Orchestrator DLQ reconciliation transitions durable request state and publishes terminal log entries to the same `log` topic; the gateway remains the materializer.
- `GetRequestHistoryByID` and `GetRequestHistoryByChangeURI` read retained request-log rows directly.

The materializer appends every audit event, selects the current authoritative winner, and repairs the queue projection. The normal orchestrator pipeline does not read or write the request-log store directly.
18 changes: 10 additions & 8 deletions submitqueue/orchestrator/README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
# SubmitQueue Orchestrator

The orchestrator runs the SubmitQueue land pipeline. It consumes the internal topics declared in `submitqueue/core/topickey/` and advances requests and batches through the stages that lead from `accepted` to a terminal state.
The orchestrator runs the SubmitQueue land pipeline. Its complete consumed-stage and publish-only topology is declared in [`pipeline.go`](pipeline.go). It consumes SubmitQueue-owned internal topics, Runway-owned result topics, and the shared hook topic.

## Pipeline stages

The pipeline is queue-driven: each stage consumes one topic, advances one entity, and publishes to the next topic.

- **start** — receives `LandRequest` from the gateway, persists the `Request` entity, and emits `Started`.
- **cancel** — records cancellation intent and hands affected batches to speculation for best-effort cancellation.
- **validate** — checks for duplicates, resolves change metadata, and publishes a `MergeRequest` to Runway's `merge-conflict-check` topic.
- **mergeconflictsignal** — correlates the dry-run result, fails the request on conflict, or forwards it to batching.
- **batch** — groups the request into a `Batch` with its dependencies.
- **speculate** — decides which speculative paths to validate (CI) versus land directly.
- **merge-conflict-check-signal** — correlates the dry-run result, fails the request on conflict, or forwards it to batching.
- **batch** — creates an inert batch attempt and hands it to dependency analysis.
- **dependency-analysis** — enrols requests, computes dependencies, and promotes the selected batch attempt.
- **speculate** — reconciles queue-wide path state, decides outcomes, and allocates speculative builds.
- **build** — triggers a CI build for a speculative path.
- **buildsignal** — records the CI result and loops back to `speculate`.
- **buildsignal** — polls or receives CI state, records the result, wakes `speculate`, and holds non-terminal deliveries until the next poll.
- **merge** — publishes a committing `MergeRequest` to Runway's `runway-merge` topic.
- **mergesignal** — correlates the merge result and fans out to `conclude` and back to `speculate`.
- **merge-signal** — correlates the merge result and fans out to `conclude` and back to `speculate`.
- **conclude** — maps the terminal batch state to the request states.
- **log** — persists gateway-owned request-log events published by the orchestrator.
- **submitqueue-hook** — dispatches lifecycle hook events to configured integrations.
- **DLQ reconcilers** — one per primary consumed topic, driving stuck requests/batches to a conservative terminal `failed` state.

See [doc/rfc/submitqueue/workflow.md](../../doc/rfc/submitqueue/workflow.md) for the full pipeline diagram and ownership rules.
The orchestrator publishes request-log entries to `log`, but does not consume or persist them; the gateway owns that stage. It also publishes full cross-service requests to Runway's merge-conflict-check and merge topics.
Loading
Loading