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,14 @@
# Context pack — #1405

- Branch: `fix/1405-durable-producer-rejection-taxonomy` at baseline `01aa12b67`.
- Archetype: 3 — Runtime / Behavior; no scope overlay.
- Locked reasons: graceful close drain is `producer-stopping`; non-retryable transport failure is
`transport-refused`; retryable failures at `maxAttempts` remain `retry-exhausted`.
- Behavior boundary: reason strings only; no acceptance, retry-count, settlement class, delivery,
cancellation, telemetry-classification, #1398, or scaffold changes.
- PLAN-EVAL: N/A per the owner brief and orchestration supervisor record.
- Implementation gates: scoped wrappers, quality gate, target quality scan, doc lint, JSR audit,
focused tests, telemetry guard, and package-configured full suite are green.
- Gate caveat: the brief's bare `deno test packages/plugin-streams-core` exits 1 because it omits
the package suite's required env permission; the configured `--allow-all` task passes 33/33.
- Orchestrator retains slice review, IMPL-EVAL, ready-state, merge, and release authority.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Drift — #1405

## 2026-08-12 — bare test command lacks package permissions

- Severity: minor gate-invocation mismatch.
- Expected: `deno test packages/plugin-streams-core` passes as written in the brief.
- Observed: it exits 1 with 19 `NotCapable` failures because tests read
`DURABLE_STREAMS_URL` and the command grants no env permission; 14 permission-free tests pass.
- Action: preserved the red evidence and ran the checked-in package task, which grants the package's
declared test permissions and passes 33/33. No test or permission surface was edited.

## 2026-08-12 — root quality gate omits the target package

- Severity: minor gate-coverage limitation.
- Expected: mandatory `quality:gate` provides changed-package quality evidence.
- Observed: it exits 0, but its configured scan/doctrine roots do not include
`packages/plugin-streams-core`.
- Action: ran an explicit target quality scan (`findings=[]`, `allowCount=0`) and target doctrine
audit. The doctrine audit has no failures and one 500-line-cap warning: the supervisor is 515
lines after this locked change (baseline 497). A structural split is outside the no-refactor slice.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Slice identity — #1405

| Field | Value |
| --- | --- |
| Lane | `light_implementation` |
| Generator | Codex · OpenAI · GPT-5.6 Sol · low |
| Worktree | `/home/codex/repos/ns006-1405` |
| Branch | `fix/1405-durable-producer-rejection-taxonomy` |
| Baseline | `origin/main@01aa12b67` |
| Issue | `#1405` |
| PLAN-EVAL | N/A — fully specified mechanical slice |
| IMPL-EVAL | Separate orchestrator-owned evaluator session |

The milestone orchestrator retains slice review, merge, publish, and release authority.
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Worklog — #1405 durable producer rejection taxonomy

## Design

### Public surface

- Add exactly `transport-refused` to `StreamWriteUnknownReasonV1`.
- Reuse the existing `producer-stopping` rejection reason during graceful close drain.
- Keep the existing root re-exports; add no entrypoint or export.

### Domain vocabulary and ports

- Closing intent is private supervisor state, visible from `close()` entry until shutdown settles.
- A non-retryable transport failure is a positive refusal; retryable failure at the attempt bound is
exhaustion.
- Existing transport, clock, random, queue, and lifecycle ports remain unchanged.

### Constants

- No new constant collection is required; the published reason unions remain the finite vocabulary.

### Commit slice

- S1 changes only reason selection and adds deterministic close/refusal/exhaustion tests. Proving
gates are the focused tests plus the complete gate set named in the slice brief.

### Deferred scope

- No acceptance, delivery, cancellation, retry-count, telemetry-classification, scaffold, #1398,
merge, or release changes.

### Contributor path

- Start with `producer-contract-v1.ts` for reason vocabulary, then follow supervisor settlement and
rejection selection into the contract behavior tests.

## Phase status

- `PLAN-EVAL: N/A` — the owner brief and orchestrator research fully specify the two selectors,
locked vocabulary, negative tests, boundaries, and gates.
- Implementation and generator gates complete on the assigned `light_implementation` lane.
- Separate orchestrator-owned slice review and IMPL-EVAL remain pending.

## Evidence

| Gate | Exit | Result |
| --- | ---: | --- |
| Focused contract behavior | 0 | 8 passed, 0 failed; includes all four new negative tests. |
| Existing telemetry classification guard | 0 | 2 passed, 0 failed. |
| Scoped check wrapper | 0 | 43 files, 1 batch, 0 failures/occurrences. |
| Scoped lint wrapper | 0 | 43 files, 1 batch, 0 occurrences. |
| Scoped format wrapper | 0 | 43 files, 1 batch, 0 findings. |
| Post-format reason grep | 0 | `transport-refused` and `producer-stopping` remain in contract, selector, and tests. |
| `deno task quality:gate` | 0 | `quality:scan` and `arch:check` completed; existing repository warnings only. The configured quality roots omit this package. |
| Explicit target quality scan | 0 | `packages/plugin-streams-core/src`; `findings=[]`, `allowCount=0`. |
| Explicit target doctrine audit | 0 | `FAIL=0 WARN=1 INFO=1`; supervisor is 515 lines versus the 500-line advisory cap, and architecture docs are informationally absent. |
| Full export-map doc lint | 0 | 4 entrypoints; `totalErrors=0`, `totalMissingJSDoc=0`. |
| JSR audit | 0 | dry-run OK; one non-failing slow-types banner warning. |
| Brief's exact `deno test packages/plugin-streams-core` | 1 | 14 passed, 19 failed solely with `NotCapable` because the command omits `--allow-env`. |
| Package-declared `deno task --cwd packages/plugin-streams-core test` | 0 | 33 passed, 0 failed. |

## Reconcile

- #1405 remains the only resolving issue; the draft PR must carry `Closes #1405` and target
`main`. No #1398 surface was touched.
- No new dependency, export-map key, lint suppression, unsafe cast, `any`, or architecture debt was
introduced.
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ export class DurableStreamProducer<TDef extends StreamStateDefinition>
context?: StreamWriteContextV1,
): StreamWriteReceiptV1 {
if (this.closed) {
return this.#reject(stateRejection(this.state.state));
return this.#reject(this.#supervisor.writeRejectionReason() ?? 'producer-failed');
}
const definition = this.#schema[entityType];
if (!definition) {
Expand Down Expand Up @@ -157,7 +157,7 @@ export class DurableStreamProducer<TDef extends StreamStateDefinition>
context?: StreamWriteContextV1,
): StreamWriteReceiptV1 {
if (this.closed) {
return this.#reject(stateRejection(this.state.state));
return this.#reject(this.#supervisor.writeRejectionReason() ?? 'producer-failed');
}
const definition = this.#schema[entityType];
if (!definition) {
Expand Down Expand Up @@ -248,18 +248,6 @@ export class DurableStreamProducer<TDef extends StreamStateDefinition>
}
}

function stateRejection(
state: StreamProducerStateSnapshotV1['state'],
): StreamWriteRejectionReasonV1 {
if (state === 'stopping') {
return 'producer-stopping';
}
if (state === 'stopped') {
return 'producer-stopped';
}
return 'producer-failed';
}

function resolveRequiredStreamUrl(streamPath: string): string {
try {
return buildStreamUrl(streamPath);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export class DurableStreamProducerSupervisor {
bufferedBytes: 0,
};
#accepted = true;
#closing = false;
#epoch = 0;
#nextSequence = 0;

Expand Down Expand Up @@ -110,6 +111,11 @@ export class DurableStreamProducerSupervisor {
return !this.#accepted;
}

/** Explain why a new write cannot be accepted. */
writeRejectionReason(): StreamWriteRejectionReasonV1 | undefined {
return this.#writeRejectionReason();
}

/** Accept an already serialized event into the bounded FIFO. */
enqueue(body: string, lifecycle?: StreamProducerWriteLifecycle): StreamWriteReceiptV1 {
const rejection = this.#writeRejectionReason();
Expand Down Expand Up @@ -213,6 +219,7 @@ export class DurableStreamProducerSupervisor {
return Promise.resolve();
}
this.#accepted = false;
this.#closing = true;
this.#closePromise = this.#closeGracefully();
return this.#closePromise;
}
Expand Down Expand Up @@ -298,7 +305,11 @@ export class DurableStreamProducerSupervisor {
if (this.#abort.signal.aborted) {
return false;
}
if (!isRetryable(connected.failure) || attempt === this.#reconnectPolicy.maxAttempts) {
if (!isRetryable(connected.failure)) {
this.#failActive(entry, connected.failure, attempt);
return false;
}
if (attempt === this.#reconnectPolicy.maxAttempts) {
this.#failActive(entry, connected.failure, attempt);
return false;
}
Expand Down Expand Up @@ -416,7 +427,11 @@ export class DurableStreamProducerSupervisor {
): void {
this.#queue.settle(active, {
status: 'delivery-unknown',
reason: failure.kind === 'aborted' ? 'transport-aborted' : 'retry-exhausted',
reason: failure.kind === 'aborted'
? 'transport-aborted'
: !isRetryable(failure)
? 'transport-refused'
: 'retry-exhausted',
error: failure.message,
});
this.#fail(`${failure.message} (attempt ${attempt})`);
Expand Down Expand Up @@ -473,8 +488,10 @@ export class DurableStreamProducerSupervisor {
return 'producer-stopping';
case 'stopped':
return 'producer-stopped';
default:
case 'failed':
return 'producer-failed';
default:
return this.#closing ? 'producer-stopping' : 'producer-failed';
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ export type StreamWriteCancellationReasonV1 = 'producer-stopped' | 'producer-fai
/** Reasons an attempted write cannot be reported as delivered or rejected. */
export type StreamWriteUnknownReasonV1 =
| 'retry-exhausted'
/** The transport positively refused the write with a non-retryable failure. */
| 'transport-refused'
| 'transport-aborted'
| 'producer-stopped';

Expand Down
Loading
Loading