Skip to content
Closed
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
42 changes: 42 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,45 @@ add CODEOWNERS-based merge gates until multiple independent maintainers exist.
- Update architecture, ADR, doctoring, CHANGELOG, and deterministic security
tests whenever artifact identity, path traversal, concurrency, portability,
or rollback semantics change.

## Provider result streaming contract

- Keep `BatchAPIClient.download_results()` source compatible and make incremental
retrieval an explicit opt-in through `StreamingBatchAPIClient`.
- Preserve inherited credentials, HTTPS validation, disabled redirects, bounded
idempotent GET retries, request timeouts, identifier validation, and total
decoded-byte limits.
- End retry eligibility before response handoff. After body iteration begins, a
transport or response-close failure must close the active response exactly
once, raise bounded body-free diagnostics, and never reopen the file or replay
records already yielded to the caller.
- Reject non-success provider-file responses before body consumption. Never put
provider bodies, record contents, URLs, credentials, or identifiers into
diagnostics, telemetry, or retained exception cause/context links.
- Consume only non-empty bounded byte chunks; reject missing streams, non-byte
chunks, zero-progress chunks, and chunks larger than the requested transport
ceiling before package-owned line buffering.
- Enforce total bytes, one physical JSONL line, and the combined
output-plus-error record count before yielding excessive data.
- Enforce `max_jsonl_physical_lines` as one batch-wide physical line budget
shared by result and error files. Count every newline-terminated or final
unterminated line, including blank lines, before UTF-8 or JSON parsing.
- Decode each nonblank line as strict UTF-8 and require one unambiguous JSON
object. Preserve deterministic output-then-error ordering, CRLF support, and
final lines without a newline; reject non-finite numbers and duplicate names.
- Raise sanitized decoder failures only after leaving the active provider
exception handler so exported errors do not retain provider bytes or text.
- Make lifecycle ownership explicit. `open_batch_records()` is the supported
boundary for consumers that may stop early; it closes the outer iterator,
nested file iterator, and active HTTP response. Never claim that a bare
`async for` break automatically calls `aclose()`.
- Keep library buffering bounded to one line and one decoded record. Document
that downstream consumers own backpressure and can recreate aggregate memory
use by collecting every record.
- Maintain 100% production statement, branch, and public-docstring coverage with
deterministic split-chunk, malformed-input, byte-limit, record-limit,
physical-line-limit, compatibility, cleanup, cancellation, post-handoff
transport, no-replay, and body-free error tests.
- Update README, architecture, ADR, operator documentation, doctoring, and
CHANGELOG whenever streaming resource, ordering, lifecycle, validation, or
compatibility contracts change.
82 changes: 72 additions & 10 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ Artifact entries are opened relative to the held final directory with
`O_NOFOLLOW | O_NONBLOCK`, required to be regular files, and streamed through
bounded `os.read`. Size and SHA-256 are derived from that same open file
description. Device, inode, file type, size, modification time, and change time
must remain stable across the read. The verifier then re-enumerates the same
held directory descriptor and rejects membership drift.
must remain stable across the read. The verifier then re-enumerates the same held
directory descriptor and rejects membership or same-name object-identity drift.

This openat-style boundary removes pathname check-then-open races from the
release identity decision. It does not prevent a same-UID process from changing
Expand All @@ -84,18 +84,72 @@ renames within the pinned final parent, and synchronizes the directory entry.
Neither verifier nor writer publishes, signs, attests, approves, or authorizes
reuse of pull-request artifacts.

## Bounded provider result boundary

`BatchAPIClient.download_results()` remains the backward-compatible aggregate
retrieval facade. `StreamingBatchAPIClient` is the opt-in memory-safety boundary
for large provider result and error files. It reuses the same credential, URL,
timeout, no-redirect, identifier, retry, and decoded-byte controls while parsing
JSONL incrementally.

The streaming client validates one terminal status snapshot, then consumes the
output file before the error file. It holds at most one non-empty bounded
transport chunk, one bounded physical line, and one decoded JSON object in
library-owned memory. A strict per-file decoded-byte limit, per-line byte limit,
and combined record limit fail closed before excessive data is yielded. Missing
streams, non-byte chunks, zero-progress chunks, and chunks above the requested
ceiling are rejected before package-owned line buffering. Non-success file
responses are rejected before provider body consumption, and every nonblank line
must be strict UTF-8 containing one unambiguous JSON object.

`max_jsonl_physical_lines` establishes one batch-wide physical line budget
shared by result and error files. Every newline-terminated line and any final
unterminated line consumes the budget before UTF-8 decoding or JSON parsing;
blank lines count even though they do not yield records. This closes a bounded
CPU-amplification gap that per-line bytes and yielded-record limits alone do not
cover.

Decoder failures are translated after the provider decoder's active exception
handler exits. The sanitized public error therefore does not retain the decoder
exception—and its provider-controlled bytes or text—through `__cause__` or
`__context__`.

Stream lifetime is a separate control-plane boundary. The outer public iterator
owns each nested provider-file iterator through `contextlib.aclosing`.
`open_batch_records()` owns the outer iterator and closes it in `finally`, making
it the supported API when a consumer may stop early. A bare `async for` break
does not call `aclose()` and is not a deterministic HTTP-response release
mechanism.

Transport retry eligibility ends before the response is handed to a body
consumer. Request acquisition and retryable HTTP-status decisions may retry only
while no response body has been exposed. Once body iteration begins, a payload
or response-close transport failure closes that response once, becomes a bounded
body-free `GatewayError`, and never reopens the provider file or replays records
already yielded. This boundary prevents an idempotent request retry from becoming
non-idempotent application delivery after partial consumption.

This boundary does not provide durable downstream backpressure. Embedding hosts
own record persistence, queue capacity, cancellation, explicit iterator
lifecycle, and consumer memory. A host that accumulates every yielded record
recreates aggregate memory use.

## Modular interoperability

CWL hosts such as `contextual-orchestrator` and `naruon` supply tenant context
only after their own authentication and authorization boundary. The package
does not require either host and retains standalone operation. When embedded,
tenant scope is a local control-plane identity and not model- or
provider-returned data.
only after their own authentication and authorization boundary. The package does
not require either host and retains standalone operation. When embedded, tenant
scope is a local control-plane identity and not model- or provider-returned data.

Release evidence also remains standalone. Host modules may consume the bounded
manifest only as review input and must not reinterpret it as provenance,
publication authority, or an integrated-release attestation.

Streaming retrieval also remains standalone. Host modules may persist each
`BatchResultRecord` into their own tenant-qualified queue or database, but must
preserve the package's file ordering, resource limits, retry-handoff boundary,
and deterministic cleanup contract or define and test a stricter local boundary.

## Verification boundary

Deterministic gates cover strict tenant validation, standalone compatibility,
Expand All @@ -110,7 +164,15 @@ granted.

Release security tests cover symlinked parents, parent traversal, artifact
replacement after enumeration, in-place mutation during streaming hash,
directory-membership drift, bounded scan and error behavior, descriptor
capability failure, Python compatibility, and 100% production statement and
branch coverage. Final merge evidence must be regenerated against the integrated
base; successful stacked-base runs are not reusable release evidence.
directory-membership and same-name identity drift, bounded scan and error
behavior, descriptor capability failure, Python compatibility, and 100%
production statement and branch coverage. Streaming tests cover split chunks,
CRLF and final-line parsing, invalid and zero-progress streams, encoding and JSON
failures, exception-context sanitization, nested and early-close lifecycle,
post-handoff payload and response-close failures, no-retry/no-replay behavior,
non-object records, non-success responses, total-download, byte-line, combined
record, and batch-wide physical line limits across result and error files,
including blank lines, deterministic result/error ordering, and 100% production
statement and branch coverage. Final merge evidence must be regenerated against
the integrated base; successful stacked-base runs are not reusable release
evidence.
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Opt-in `StreamingBatchAPIClient` and immutable `BatchResultRecord` for
output-then-error JSONL iteration without whole-body or whole-result-list
materialization, with strict per-file decoded-byte, physical-line byte,
batch-wide physical line, and combined record-count limits. Result and error
files share the physical-line budget, and blank lines consume it before
parsing; split UTF-8, CRLF, and final lines without a newline are handled
deterministically, while invalid streams, encodings, JSON, non-object records,
and non-success file responses fail closed with body-free diagnostics.
`open_batch_records()` provides deterministic context-managed ownership for
consumers that may stop before exhaustion.
- Read-only exact-head release acceptance that builds one wheel and source
distribution twice from clean Git archives, proves byte-identical SHA-256
identity, records bounded canonical evidence, and keeps publication and
Expand Down Expand Up @@ -36,6 +46,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

- Stopped idempotent GET retries at response handoff so post-handoff payload or
response-close failures close once and cannot reopen provider files, duplicate
already-yielded records, or violate the asynchronous-context-manager protocol.
- Closed active provider-file responses deterministically after context-managed
early exits, explicitly closed nested asynchronous generators, rejected
zero-progress empty adapter chunks, and removed provider-controlled decoder
bytes and text from sanitized parser exception cause and context links.
- Bound release-directory traversal, artifact open, bounded streaming hash, size
derivation, and final membership validation to held descriptors; reject parent
symlinks, `..` traversal, artifact replacement, in-place identity drift, and
Expand Down Expand Up @@ -90,6 +107,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- Exposed `ValidationError.field`, `.value`, and `.reason` as direct stable
attributes while retaining the existing structured `details` dictionary.
- Migrated package licensing to PEP 639 with an SPDX `Apache-2.0` expression,
explicit `LICENSE` and `NOTICE` files, the `uv_build` backend, and exact
`uv`/`uv_build` 0.12.1 governed build pins so PEP 517 backend selection cannot
Expand Down
42 changes: 42 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,45 @@
- Maintain test-first concurrency, unsupported-platform, bounded-enumeration,
identity, documentation, and rollback contracts with 100% production
statement, branch, and public-docstring coverage.

## Provider result streaming invariants

- Preserve the aggregate `BatchAPIClient.download_results()` contract; use the
opt-in `StreamingBatchAPIClient` for incremental output.
- Inherit and preserve credential lookup, HTTPS URL validation, disabled
redirects, bounded idempotent GET retry, timeouts, provider identifier
validation, and decoded-byte limits.
- Finish request-acquisition and retryable-status decisions before response
handoff. Once body iteration starts, a transport or response-close failure
closes the active response exactly once and must never reopen the file or
duplicate records already yielded.
- Check the final provider-file HTTP status before consuming its body. Keep
failure diagnostics body-free and free of credentials, URLs, identifiers,
record data, and retained decoder exception payloads.
- Consume only `iter_chunked` byte streams and count `memoryview.nbytes`. Reject
absent streams, non-byte chunks, empty zero-progress chunks, and chunks larger
than the requested transport ceiling before package-owned buffering.
- Enforce total bytes, physical-line bytes, and the combined result-plus-error
record count before excessive data is yielded.
- Validate and enforce `max_jsonl_physical_lines` as a batch-wide physical line
ceiling shared across result and error files. Count blank and nonblank lines,
including a final unterminated line, before decoding or parsing.
- Decode each nonblank physical line as strict UTF-8 and require one unambiguous
JSON object. Preserve output-before-error order, CRLF handling, and final
records without a terminating newline; reject non-finite numbers and duplicate
object names.
- Translate decoder failures after leaving the active provider exception handler
so exported `GatewayError` objects have no cause or context retaining provider
bytes or text.
- Use `open_batch_records()` when a consumer may stop early. It owns and closes
the public iterator, each nested file iterator, and the active HTTP response.
Never rely on a bare `async for` break to call `aclose()` automatically.
- Keep library-owned memory bounded to one line and one decoded record. Treat
downstream collection, persistence, transformation, lifecycle ownership, and
backpressure as host responsibilities.
- Maintain 100% production statement, branch, and public-docstring coverage with
deterministic split-chunk, invalid-stream, zero-progress, invalid-encoding,
malformed-JSON, exception-sanitization, early-close, nested-close,
post-handoff payload and close failure, no-replay, record-limit,
byte-line-limit, batch-wide physical-line-limit, download-limit,
compatibility, and error tests.
Loading
Loading