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
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Post-merge evidence — sink-breaker-terminal-isolation

Verified 2026-11-05.

| Row | Check | Result |
|---|---|---|
| PR | Pull request #532 `:bug: fix(sink): keep one inventory's ownership conflicts out of the shared breaker` | all checks on head `6cc25f4b9` green or skipped (45 pass, 1 skipped); merge state CLEAN; merged by rebase |
| Merge | Head of `main` after the merge | `850df538d` (the bot's changelog sync followed as `ae1be083e`) |
| Main CI | Runs on `main` whose `headSha` is `850df538d` | 8 runs, all `success`: CI 42030314133, E2E smoke 42030314193, Docs 42030314421, Preflight 42030314186, CodeQL 42030314100, OpenSSF Scorecard 42030314223, Code Quality 42030312333, changelog-sync 42030314290 |
| Archive | `task spec:cli -- archive sink-breaker-terminal-isolation --yes` | see the archive commit; living spec `openspec/specs/sink-circuit-breaker/spec.md` gains SCB-1..SCB-3 |

Commands:

```sh
gh pr view 532 --json statusCheckRollup,mergeCommit
gh run list --branch main --limit 40 --json databaseId,headSha,name,status,conclusion \
--jq '.[] | select(.headSha == "850df538d3a4fe19aae9044a6de076a944db6462")'
```
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Loop — sink-breaker-terminal-isolation (spec-loop-duo run state)

Repo: `/home/koni/Projects/PlatformRelay/worktrees/kollect/sink-breaker-terminal-isolation` · Branch: `fix/sink-breaker-terminal-isolation` · Base: `origin/main` @ `96c41b0d0` · Started: 2026-11-05 · Reviewers: two-model duo
Next: 4.1 integrator — push, PR, merge on green CI, post-merge evidence, archive
Next: done — merged in #532 (`850df538d`, main CI green by headSha), archived 2026-11-05 (`evidence/post-merge.md`)
Task prompt: local, `agent-context/loops/sink-breaker-terminal-isolation/task-prompt.md` (harness, not committed) · decisions by model alignment, logged in agent-context/decisions.md
Budget: paid review legs 4/12 (counted from legs.log: R1, R2, B1, B2 spec legs; no per-task paid leg) (B reserve 3; default) · active hours 0/8 (sessions: 2026-11-05) · per task estimate 0 legs (no extra-leg trigger expected: no secrets, CI, infra or manifests), R 1, B 2 → planned 3

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,6 @@ Test rules for every group:

## 4. Integration

- [ ] 4.1 Integrator (orchestrator): branch review (stage B), PR with the release-note line, merge on
- [x] 4.1 Integrator (orchestrator): branch review (stage B), PR with the release-note line, merge on
review + green CI, post-merge evidence, archive the change, update BACKLOG (SINK-BREAKER-TENANT-01,
RACE-COUNT-BREAKER-ISOLATION).
90 changes: 90 additions & 0 deletions openspec/specs/sink-circuit-breaker/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# sink-circuit-breaker Specification

## Purpose
Which export failures count toward a sink's shared circuit breaker, and how the breaker behaves
closed, open and half-open. The breaker sheds load from a sink that is failing for every inventory;
a failure that belongs to one inventory (an ownership conflict) must not open it for the others.

## Requirements

### Requirement: SCB-1 Inventory-scoped export failures do not count toward the sink breaker

The shared per-sink circuit breaker SHALL exclude an export plan result that carries the
inventory-scoped marker (`errors.Is(err, kollecterrors.ErrInventoryScoped)`): it SHALL count neither
as a failure nor as a success, and SHALL NOT reset the consecutive-failure count. The marker SHALL
make the error Terminal and keep its text.

#### Scenario: One inventory's ownership rejections do not open the breaker

- **GIVEN** a Git sink with a closed breaker
- **WHEN** one inventory's export to that sink is rejected for ownership 10 times in a row
- **THEN** the breaker stays closed
- **AND** another inventory's next export to the same sink runs against the backend and succeeds

#### Scenario: The marker survives to the breaker

- **GIVEN** a Git sink whose export plan rejects an inventory for ownership
- **WHEN** the export returns
- **THEN** the error carries the inventory-scoped marker and is Terminal

#### Scenario: An excluded result does not break a streak of counted failures

- **GIVEN** a sink with a closed breaker
- **WHEN** exports fail Transient, inventory-scoped, Transient, Transient, Transient, Transient
- **THEN** the breaker opens after the fifth Transient failure

### Requirement: SCB-2 Every other export failure still counts toward the sink breaker

The breaker SHALL count every non-nil export plan result without the marker as a failure (Transient,
Forbidden, Terminal, and errors without a class) and SHALL open after 5 consecutive ones within its
closed-state counting interval. While open, an export to the sink SHALL NOT reach the backend and SHALL
fail with a Transient error naming the open breaker.

#### Scenario: Five transient failures open the breaker

- **GIVEN** a sink with a closed breaker
- **WHEN** 5 consecutive exports fail with a Transient error
- **THEN** the sixth export does not call the backend
- **AND** it fails with a Transient error whose message contains "circuit breaker open"

#### Scenario: Sink-wide Terminal failures open the breaker

- **GIVEN** a Git sink with a closed breaker
- **WHEN** 5 consecutive exports fail with an authentication error the Git backend classifies Terminal
- **THEN** the sixth export does not call the backend

#### Scenario: Unclassified and Forbidden errors count

- **GIVEN** a sink with a closed breaker
- **WHEN** 5 consecutive exports fail with an error that carries no class, or with a Forbidden error
- **THEN** the sixth export does not call the backend

### Requirement: SCB-3 An inventory-scoped result in half-open neither closes nor wedges the breaker

When the breaker is half-open, an export whose result is inventory-scoped SHALL leave it half-open
and SHALL release the trial slot, so the next export is admitted as the trial. While a trial is in
flight, a concurrent export SHALL NOT reach the backend. A successful trial SHALL close the breaker; a
counted failure on the trial SHALL re-open it.

#### Scenario: Inventory-scoped trial, then a successful trial

- **GIVEN** a breaker that opened on Transient failures and whose open timeout has elapsed
- **WHEN** the trial export fails inventory-scoped
- **THEN** the breaker is half-open
- **WHEN** the next export succeeds
- **THEN** that export reached the backend
- **AND** the breaker is closed afterwards

#### Scenario: Inventory-scoped trial, then a failing trial

- **GIVEN** a breaker that opened on Transient failures and whose open timeout has elapsed
- **WHEN** the trial export fails inventory-scoped
- **AND** the next export fails with a Transient error
- **THEN** the breaker is open afterwards

#### Scenario: Concurrent export during a trial

- **GIVEN** a half-open breaker whose trial export is in flight
- **WHEN** a second export starts before the trial finishes
- **THEN** the second export does not reach the backend
- **AND** after the trial finishes inventory-scoped, the next export is admitted
Loading