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
27 changes: 27 additions & 0 deletions docs/specs/guarded-approval-http.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Guarded approval HTTP client

This opt-in, unpublished core client wraps the [guarded data contract](guarded-approval-contract.md). Existing delivery, authoring and localization APIs stay unchanged. A compatible server must verify actual identity, current admission and receipt ownership. JWT shape validation and response parsing establish none of those facts.

## Interface and configuration

`createLingvaGuardedApprovalHttpClient({endpoint,getBearerToken,fetcher?,timeoutMs?})` returns `getContext(scope,{signal?}?)` and `approve(command,{signal?}?)`. They resolve to the existing context/approval response types. `getBearerToken(signal)` supplies a raw string synchronously or asynchronously. Configuration is captured when the factory runs. Each invocation validates, snapshots and serializes its input before awaiting credentials, without freezing or mutating caller data.

Endpoints are explicit absolute HTTPS URLs, or HTTP with literal `127.0.0.1` or `[::1]` hosts for owned local tests. Reject userinfo, relative URLs, queries and fragments, including empty delimiters. Endpoint and injected Fetch are trusted configuration, not a general SSRF sandbox. There is no hosted default, ambient environment/cookie credential, arbitrary RequestInit, OAuth flow, retry, polling, UUID generation, persistence or legacy fallback.

Send one POST with the exact existing builder payload and explicit Authorization Bearer and JSON content-type headers. Set `redirect:error`, `credentials:omit`, `cache:no-store`, `referrerPolicy:no-referrer`. Reject empty, prefixed, whitespace/control-bearing, non-three-part-base64url or over16KiB UTF-8 token strings locally. Tokens and claims never enter business data, errors or retained client state beyond the active invocation.

## Bounds and observations

Default timeout is5000ms; optional timeout is an integer1..5000ms. One application deadline covers credential acquisition, Fetch and complete body consumption, even if a dependency ignores abort. Caller cancellation composes with it. Dispose timers/listeners and safely cancel/release readers on exit. Cancellation never proves remote rollback.

Read successful bodies as byte streams, with an actual128KiB ceiling independent of Content-Length. Require strict UTF-8 JSON and use the existing parser against captured scope or UUID. Missing bodies, malformed data and mismatches fail. Return a fresh known-field projection, retaining recognized optional fields. Approval success acknowledges only the UUID; captured command supplies its target association. Non-2xx bodies are discarded/canceled and never exposed.

`LingvaGuardedApprovalHttpError` carries static message, `kind`, optional numeric `status`, `phase:local|request`, and `recovery:none|keep-command`. It contains no URL, token, raw exception/cause, server text, scope, subject, translation or command. HTTP400/413 map to invalid-request;401 unauthenticated;403 forbidden;404 not-found;409 conflict;5xx unavailable. Unexpected statuses or malformed successes are invalid-response. Local validation is invalid-request; a throwing credential callback is unavailable; network/cancellation/deadline is transport. Phase becomes request only when Fetch is invoked. All failed approve attempts, including local validation and401/403/409, carry keep-command. Context failures carry none. Factory errors are local/none.

## Intent recovery

The caller owns captured intent and authenticated project/account scope. After any failed approve, retain the same command, UUID and context token. A later explicit invocation may obtain a rotated credential but sends the identical body. Never refresh context, remint a UUID or change target automatically. A locally rejected retry says nothing about an earlier uncertain outcome. Only a valid matching acknowledgment resolves the captured command. No persistence across navigation or session changes is provided.

## Validation and exclusions

Tests use synthetic credentials and injected Fetch, plus owned loopback HTTP servers for default Fetch redirect and receipt-recovery contracts. A loopback receipt simulation is not proof of a real server transaction or database atomicity. Packed ESM/CJS/TypeScript consumers prove exports. No server source, package version/license/dependency change, package publication, existing caller adoption, provider call or deployment belongs to this batch. Removing the additive helper/exports reverses this unpublished candidate.
92 changes: 92 additions & 0 deletions docs/superpowers/plans/2026-10-29-guarded-approval-http.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Guarded Approval HTTP Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [x]`) syntax for tracking.

**Goal:** Add an opt-in bounded core HTTP client for guarded context and exact-command approval recovery.

**Architecture:** A focused transport module reuses existing builders and parsers. One attempt owns its deadline, abort composition, Fetch and bounded byte reader. Existing delivery and authoring owners are unchanged.

**Tech Stack:** TypeScript, standard Fetch/AbortController/ReadableStream, Vitest, Node owned-loopback HTTP, protected package build scripts.

**Spec:** `docs/specs/guarded-approval-http.md`

## Global Constraints

- Native inline execution in the existing isolated SDK worktree; one fresh whole-branch review at the end.
- Default5000ms; optional integer1..5000ms;16KiB credentials;128KiB streamed response. Existing16KiB request codec bound applies.
- No automatic retry, legacy adoption, persistence, server changes, dependency/version/license changes, package publication or hosted calls.
- Public documentation and fixtures are self-contained. Credentials are synthetic; network tests use owned loopback only.

## Review Focus

- Caller/config mutation during deferred credentials must not change the captured wire or validation target (Task1).
- A dependency that ignores abort and settles late must not retain a response reader or create an unhandled rejection (Task1).
- Many empty stream chunks must not starve the application deadline or retain unbounded chunk objects (Task1).
- Errors thrown by dependencies, even public error instances with sensitive text, must not escape sanitized classification (Task1).
- Uncertain acknowledgment followed by denied retries must preserve the original intent until explicit matching success (Task2).

### Task 1: Portable bounded HTTP client

**Files:**

- Create: `packages/core/src/guarded-approval-http.ts`
- Create: `packages/core/src/guarded-approval-http.test.ts`
- Modify: `packages/core/src/index.ts`

**Interfaces:**

- Consumes existing guarded scope/command/builders/parsers and response types.
- Produces `createLingvaGuardedApprovalHttpClient(options: LingvaGuardedApprovalHttpOptions): LingvaGuardedApprovalHttpClient`, methods `getContext(scope,options?): Promise<LingvaGuardedApprovalContextResponse>` and `approve(command,options?): Promise<LingvaGuardedApprovalResponse>`.
- Exports error class and named error-kind constants, timeout/token constants, options/client/call-options/kind/phase/recovery types. Error fields are readonly `kind`, `status?`, `phase`, `recovery`.

- [x] Write tests importing the public index namespace; assert factory/error exports exist, literal POST wire, headers/options, recognized context projection and matching acknowledgment. Test deferred credential/config/caller mutation, input/endpoint/token limits, zero network calls on invalid input and safe errors for every status/classification.
- [x] Add byte-stream tests: exact128KiB success/one byte over, split multibyte UTF-8, invalid UTF-8/JSON/missing body, wrong full locator/UUID, ignored Content-Length, cancellation and released locks. Add stalled callback/Fetch/body, pre/mid abort, late responses/rejections, empty-chunk deadline, timer/listener cleanup and at most one network call.
- [x] Run `pnpm --filter @lingva/core test -- src/guarded-approval-http.test.ts`.
Expected: assertion failure because factory export is missing, not a missing-module error.
- [x] Implement the interface in the new module. Capture config at factory creation and payload/body before the first await. Use a single application deadline and internal-only failure classification; never trust dependency error text. Check elapsed monotonic time between stream chunks as well as timer abort. Cancel/discard late responses without awaiting unbounded cleanup.
- [x] Run `pnpm --filter @lingva/core test` and core lint/typecheck.
Expected: existing250 plus all new tests pass; static gates pass.
- [x] Commit client, exports, tests, self-contained spec and this plan.
- [x] Completion command: `pnpm --filter @lingva/core test`.

### Task 2: Real Fetch and packaged-consumer contracts

**Files:**

- Create: `packages/core/src/guarded-approval-http.loopback.test.ts`
- Create: `packages/core/test/guarded-approval-http-consumer.cjs`
- Modify: `packages/core/test/guarded-approval-consumer.types.ts`
- Modify: `scripts/verify-public-packages.mjs`

**Interfaces:**

- Consumes Task1 factory/error/types and unchanged codec scope/command types.
- Produces owned-loopback contract tests and async ESM/CJS/TypeScript package smoke, without an actual server implementation.

- [x] Write owned-loopback tests for default Fetch POST/header/body, redirect target hit count0, no cookie/credential fallback, stalled body deadline and simulated receipt/lost-ack followed by401/403/409 and explicit identical-command success. Assert one simulated commit, body/UUID equality and no automatic requests. Write packed consumer smoke asserting new exports, both operations and static error fields; add compile-time invalid options/identity override assertions.
- [x] Run core loopback tests; run new smoke against baseline main built core.
Expected: loopback tests pass on Task1 behavior; baseline packed smoke fails with missing-export assertion, proving consumer wiring before modification.
- [x] Wire async HTTP smoke into both packed ESM and CJS entrypoints; preserve existing codec smoke and all11 package checks.
- [x] Run `pnpm --filter @lingva/core test`, `pnpm build:packages`, `pnpm verify:packages`.
Expected: all core cases and11 packed consumers including ESM/CJS/TypeScript pass; no registry publication occurs.
- [x] Commit owned-loopback and consumer contracts.
- [x] Completion command: `pnpm --filter @lingva/core test`.

### Task 3: Whole-workspace verification and delivery record

**Files:**

- Create: `docs/verification/guarded-approval-http.md`
- Modify: plan checkboxes after observed completion.

**Interfaces:**

- Consumes Task1 client and Task2 test/package contracts.
- Produces dated local verification record with exact counts, limitations and one public candidate PR.

- [x] Run `pnpm test`, `pnpm lint`, `pnpm typecheck`, `pnpm test:dependency-security`, `pnpm audit:prod` and inspect results. Run build/packages again only if changes invalidate prior artifacts.
Expected: workspace gates pass; audit records existing documented patched-artifact exceptions accurately.
- [x] Verify unchanged legacy codec/authoring/delivery sources, manifests/licenses/dependency pins and original SDK checkout. Record command results and loopback simulation limits.
- [x] Commit verification record. Completion command: `pnpm --filter @lingva/core test`.
- [x] Dispatch one fresh whole-branch review after all task gates. Regrade findings by user effect; fix Important/Critical once with genuine regression RED/GREEN and green suite. Ledger minors and every ruling.
- [x] Publish one public SDK PR and attach it. Delivery: [candidate PR12](https://github.com/usausoft/lingva/pull/12). Project reconciliation and exact-head read-back are recorded in delivery evidence. No successor roadmap PR or SDK merge in this batch.
27 changes: 27 additions & 0 deletions docs/verification/guarded-approval-http.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Guarded approval HTTP local verification

29 October2026. Candidate behavior is specified in [the HTTP contract](../specs/guarded-approval-http.md). All credentials and data below are synthetic. Existing callers remain on their existing APIs.

## Observed gates

- Core: 330 tests in 12 files, including 65 injected-Fetch cases, 8 owned-loopback cases and 7 lifecycle regression cases. The250 existing core cases remain green. The missing public factory produced genuine assertion RED before implementation; the old built package produced a missing-export assertion RED before adding packed HTTP consumer coverage.
- `pnpm test`:31 workspace tasks passed. `pnpm lint`:28 tasks plus repository lint passed. `pnpm typecheck`:29 tasks passed.
- `pnpm build:packages`:11 protected public package builds passed. `pnpm verify:packages`:all11 packed consumers passed, including the new guarded HTTP ESM/CJS behavior and TypeScript positive/negative contracts. No registry publication command ran.
- `pnpm test:dependency-security`:23 passed. `pnpm audit:prod` exited0 with the existing two reviewed patched-artifact exceptions (one low, one high). This is not an exception-free audit; see [dependency repair evidence](../specs/workspace-dependency-security.md).
- Legacy codec, authoring and delivery sources, package manifests, lock/workspace files and license files match the branch base (51 preserved files). Original SDK checkout remains clean at its prior revision.

## Transport evidence

Injected tests cover literal guarded wire, captured configuration/intent before deferred credentials, token and endpoint validation, byte ceilings, multibyte boundaries, malformed observations, static failure projection, stalled dependencies, caller abort, late resolution/rejection and reader/timer/listener cleanup. An endless empty-chunk stream terminates through elapsed-time checks without accumulating chunks.

Default Fetch against owned loopback servers sends explicit bearer credentials without cookies. Five redirect statuses301/302/303/307/308 each hit their source once and never reach the destination. A real body stalls after headers and fails within the configured budget. A receipt fixture simulates one commit and lost acknowledgment, then a local invalid-credential attempt and HTTP401/403/409; final explicit credential rotation sends the identical command/UUID and receives the original acknowledgment. There are five network attempts and one simulated commit. No hidden retry occurs.

The receipt fixture is a transport contract simulation, not evidence of any server's authorization or database atomicity. Parsing JWT shape is not identity verification. Cancellation does not establish rollback. Callers retain their captured command until a matching success.

## Delivery boundary

This remains an unpublished opt-in candidate. Package versions, licenses and existing API behavior are unchanged. Server implementation, existing caller adoption, automatic recovery persistence, provider operations and deployment are excluded. One fresh whole-branch review identified four Important lifecycle issues and one interoperability issue, promoted to Important because valid cross-realm byte chunks must work. All five were corrected in one pass with seven assertions observed failing against the original implementation, then passing with the complete 330-case suite. No findings remain deferred.

The corrections replace shared interruption-promise reactions with removable subscriptions, attach rejection handlers before cancellation checks, capture response ownership at fulfillment, make cleanup nonthrowing, and accept cross-realm Uint8Array chunks. A forced-GC subprocess retained all 10,000 empty chunks before the correction; after correction its assertion permits at most two while the next read remains stalled. Other regressions cover synchronous abort during credential/Fetch entry, cancellation immediately after response fulfillment, throwing body/listener cleanup and foreign-realm bytes. Subprocesses load current source with the declared Vite transformer in middleware mode without listening; they do not rebuild shared dist during workspace compilation. Protected package builds and packed consumers run separately.

The final workspace, static, protected-build, packed-consumer, dependency-security and audit gates above were rerun after corrections. Server identity/admission, receipt retention and database atomicity remain outside this client and unproven by these tests. Existing caller adoption and cross-session persistence remain caller-owned; this additive client does not supply them.
Loading
Loading