Skip to content

feat(lab): CL-01 deterministic protocol conformance harness (CL-00 #1286 aligned) - #10

Closed
Wibias wants to merge 85 commits into
devfrom
feat/cl-01-conformance-harness
Closed

feat(lab): CL-01 deterministic protocol conformance harness (CL-00 #1286 aligned)#10
Wibias wants to merge 85 commits into
devfrom
feat/cl-01-conformance-harness

Conversation

@Wibias

@Wibias Wibias commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add src/lab/conformance/ deterministic protocol conformance harness loading CL-00 022_protocol_v1_cases.json fixtures
  • Execute 24 canonical scenarios across responses-core, chat-core, anthropic-core, tools-core, and codex-core using shipped parseRequest, chat/responses adapters, and bridgeToResponsesSSE / anthropic outbound translation
  • Add closed assertion DSL evaluator, observation normalization, and eight negative-control fixtures proving known defects are rejected
  • Align minimal openai-chat wire semantics for developer role preservation and single-tool required allowed_tools mapping (CL-00 contract)
  • Add tests/lab-conformance-harness.test.ts harness self-tests

Verification

  • bun x tsc --noEmit
  • bun test tests/lab-conformance-harness.test.ts (10/10; 24 canonical + 8 negative controls)
  • bun test tests/bridge.test.ts tests/responses-parser.test.ts tests/chat-completions-endpoint.test.ts (128/128)
  • Full bun run test (not run; known Windows/Bun host failures documented in CL-00 stack status)

Checklist

  • CL-01 scope only (no live probes, ledger, CLI, UI, routing-profile controls)
  • CL-02 not started
  • Independent acceptance review recorded in 051_cl01_acceptance_review.md
  • Stack status updated in 001_pr_stack_status.md

Base: accepted CL-00 tip c2113ca47b8a05c5a5f90679e4eaa640ca2c6a66

iF2007 and others added 30 commits August 8, 2026 18:31
DeepSeek routed models were rebuilt with a 128k context window after
every sync because the registry entry had no jawcodeBundle, so the
catalog metadata restore step could not resolve the provider and the
strict-fields fallback overwrote the window. Users who saved the
provider under a title-cased key ("DeepSeek") also missed the alias
table, which was case-sensitive.

Add jawcodeBundle: "deepseek" to the registry entry and fold provider
case in resolveJawcodeProvider, then regenerate the metadata snapshot
with the official 1,048,576-token window for both V4 models. Routed
deepseek entries now restore 1,048,576 instead of the 128k fallback.
deepseek-chat/deepseek-reasoner were deprecated upstream on 2026-07-24 15:59 UTC
and official identifiers are now deepseek-v4-flash/deepseek-v4-pro. The aliases
stay in the registry only as compatibility aliases so existing saved configs
and requests keep validating and routing; the comment now documents that
post-deprecation reason instead of a stale future-dated note.
…a pipeline

The metadata pipeline was de-jawcoded upstream: the bundled snapshot now
lives at scripts/model-metadata.source.json and generates
src/generated/model-metadata.ts. Move the DeepSeek V4 context-window fix
onto that pipeline (1048576 for flash and pro per DeepSeek's official
Codex setup docs) and regenerate the committed output so routed rebuilds
stop falling back to the 128k strict-fields default.
…ty list

Add deepseek to the catalog augmentation allowlist so metadata-sourced V4
rows are appended when live discovery returns nothing, and cover the
empty-discovery path with a regression test asserting the official
1,048,576 context window, auto-compact limit, and text-only input.
…tadata

fix(catalog): restore DeepSeek V4 context window on routed rebuilds
Fixed 1 file(s) based on 1 unresolved review comment.

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
…e-fallback

feat(routing): fail over between policy candidates on retryable failures
…surface-parity

test(routing): lock policy evidence parity across inbound protocols
…ing-quota

feat(routing): use pool-aware quota evidence for policy profiles
Freeze the CL-00 evidence, verdict, scenario, identity, privacy, and incident contracts so conformance implementation can proceed without duplicating routing authority.
Record the accepted implementation head and draft pull request so the programme ledger is complete without authorizing CL-01.
Remove Markdown hard-break spaces so the final branch diff passes repository whitespace checks.
…lidge-jun#1287)

Replaces the full-tail allocation in the request-history indexer with a
64 KiB streaming reader. The previous `readCompleteTail` allocated
`size - indexedOffset` bytes in one shot before parsing, so a large append
created a proportional transient allocation even though the SQLite index is
a disposable projection.

Records are now assembled across chunk boundaries, and a complete record
above 1 MiB is omitted from the projection only. `usage.jsonl` stays
canonical and is never truncated or rewritten; `indexedRows` still counts
successfully projected records, and the indexed offset only advances past a
newline so a torn final record is re-read rather than skipped.

`insert.finalize()` remains unconditional in `finally` — an unterminated
prepared statement keeps the DB file busy on Windows after close.

Republished from lidge-jun#1189 by luvs01, whose branch was 300 commits behind dev.
Rebased onto f5147cb with no conflicts; authorship preserved below.

Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>
…jun#1169) (lidge-jun#1289)

`ocx codex-shim install` reported a clean green success even when the
launcher was installed but Codex routing was not provably pointed at
OpenCodex — an external `model_provider`, a user-owned local or remote
gateway, or routing that cannot be verified. It now reports a warning for
those cases.

It also warns when outbound proxy variables exist only in the current
process while `config.proxy` is unset or unresolved, because Codex launchers
and background services such as launchd may not inherit that environment.
Proxy values are never printed.

The change is advisory only: install still succeeds with the same exit code,
and the shim still fail-open execs the real Codex launcher.

Republished from lidge-jun#1169 by TyroneXie, whose branch was 335 commits behind dev.
Rebased onto f5147cb with no conflicts; authorship preserved below.

Co-authored-by: TyroneXie <328347833@qq.com>
)

OpenCodex still showed the legacy 30-day Grok billing window while the
real SuperGrok gate is weekly. Prefer GET /v1/billing?format=credits and
keep monthly /v1/billing only as fallback when weekly data is unavailable.

Closes lidge-jun#1283
…dge-jun#1288)

* fix(routing): keep unbound account quota unknown (lidge-jun#1195)

Policy profiles choose a provider and model before the request path resolves
Pool/Direct identity, thread affinity, Anthropic session affinity, or
round-robin/fill-first selection. Attaching the process-global active account
during policy evaluation could therefore score or exclude a candidate using
account A's quota and then execute the request on account B.

An unbound candidate now stays quota-unknown in both the live route trace and
the management dry-run, which is more accurate than inventing an account
reference and keeps account selection, cooldowns, and session affinity
authoritative. Unknown quota already has an explicit profile policy.
Explicit `codexAccountId` and account-ref evidence remains unchanged.

Republished from lidge-jun#1195 by luvs01, whose branch was 300 commits behind dev.
Rebased onto f5147cb with no conflicts; authorship preserved below.

Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>

* test(routing): prove the management dry-run leaves unbound candidates unknown

Maintainer-added coverage for the lidge-jun#1195 republish. The contributor's patch
deletes the same block from the live router and the management dry-run path,
but only the live path had a regression. The existing dry-run test covers a
candidate with an explicitly supplied codexAccountId, which stays known and
is unaffected by the fix, so the dry-run half of the parity claim was
unproven.

These two tests exercise an unbound Codex candidate with an active pool
account, and an unbound Anthropic candidate with an active account, and
assert both stay quota-unknown with no accountRef. Restoring either deleted
block fails them.

---------

Co-authored-by: luvs01 <27862058+luvs01@users.noreply.github.com>
…) (lidge-jun#1293)

Removing a provider deleted only `config.providers[name]`. Its rows in
`config.customModels` stayed behind, and neither consumer filters them:
`/api/models` lists every row and the generated Codex catalog emits every
row keyed by slug, so the dashboard kept advertising models that resolved
to a provider which no longer existed.

This is an inconsistency rather than a missing feature. Provider *rename*
already maintains the array — `rewriteProviderReferences` rewrites
`customModels[].provider` alongside combo targets and Claude tier maps — so
the array is meant to track the provider lifecycle, and one of the two
sibling operations simply did not. `dropProviderCustomModels` is therefore
placed next to the rename pass, so the two stay visible to each other.

Both removal paths call it and report the count: `ocx provider remove`
prints it and adds `droppedCustomModels` to `--json`, and the management
DELETE returns the same field. An emptied list drops the `customModels`
field, matching the add/remove routes; the `customModelCatalogMigration`
marker is deliberately preserved, since rewriting it would change an older
binary's view of one-time row ownership.

Partial fix for lidge-jun#1273. The second defect in that report — a stale in-memory
config re-persisting deleted rows through a whole-document write — is not
addressed here and remains open; it needs a reconciliation design of its
own, keyed on the immutable `OcxCustomModel.id` rather than on a slug.

Reported by @gdxnpy with a reproduction and a before/after config diff that
showed the cooperating save path itself was healthy, which is what made the
second defect findable rather than "settings sometimes revert".
…ck (lidge-jun#1305)

A combo member whose provider row is incomplete — missing a context window,
or absent from the fetched catalog — was dropped from the generated Codex
catalog rather than synthesized. `resolveComboCatalogMember` fills the gap
from configuration, falling back to the provider's declared max input and
then to 128k, so a combo stays selectable when one member's upstream row is
thin.

Republished from lidge-jun#1163 by 关俊江, whose branch was 366 commits behind dev.
Two conflicts, both mechanical and both on a single line: `dev` renamed
`augmentRoutedModelsWithJawcodeMetadata` to `augmentRoutedModelsWithMetadata`
and added `CODEX_ACCOUNT_BOUND_CATALOG_KIND` plus a `catalog/parsing` import
block, while this branch added `resolveComboCatalogMember` to the same export
and import lines. Resolved by keeping every symbol from both sides; no
behavior was re-decided.

Co-authored-by: 关俊江 <each1024@qq.com>
* fix(google): discover Antigravity live models

* fix(security): guard Antigravity model discovery

Route Cloud Code Assist discovery POSTs through the provider outbound policy and invalidate account-scoped live model cache when OAuth credentials change.

* fix(google): scope Antigravity discovery to current account

Discard stale discovery writes after OAuth account changes, use the routed CCA project for discovery, and retain partial Gemini availability as explicit wire IDs.

* fix(google): harden Antigravity catalog discovery

Invalidate removed provider generations, migrate canonical static opt-outs, and reject malformed CCA model identifiers.

* fix(antigravity): harden live model discovery

* fix(catalog): distinguish cache eviction from authority changes
@Wibias Wibias changed the title [WRONG BRANCH] feat(lab): CL-01 deterministic protocol conformance harness feat(lab): CL-01 deterministic protocol conformance harness (CL-00 #1286 aligned) Aug 8, 2026
@Wibias
Wibias changed the base branch from feat/cl-00-compatibility-contracts to dev August 8, 2026 22:51
@Wibias
Wibias force-pushed the feat/cl-01-conformance-harness branch from 565f399 to 22d608c Compare August 8, 2026 22:51
@Wibias

Wibias commented Aug 8, 2026

Copy link
Copy Markdown
Owner Author

Superseded by the upstream CL-01 PR against lidge-jun/opencodex:dev: lidge-jun#1320

This fork-local PR targeted Wibias/opencodex:dev and could not be retargeted to the correct upstream base repository. The CL-01 branch (feat/cl-01-conformance-harness) remains unchanged at d665004.

@Wibias Wibias closed this Aug 8, 2026
@Wibias Wibias reopened this Aug 9, 2026
@Wibias Wibias closed this Aug 9, 2026
@Wibias
Wibias deleted the feat/cl-01-conformance-harness branch August 9, 2026 04:10
@Wibias
Wibias restored the feat/cl-01-conformance-harness branch August 9, 2026 09:30
Wibias added a commit that referenced this pull request Aug 11, 2026
…ice grant + free/paid live catalog (lidge-jun#1397)

* feat(providers): add Nous Portal (Nous Research) OAuth provider — device grant + free/paid live catalog (Closes lidge-jun#1148)

* feat(providers): seed Nous Portal free models from live Portal list (hy3, laguna-s/xs, step-3.7-flash)

* test(nous-oauth): cover device-flow error paths and refresh-token fallback

- access_denied / expired_token surface as terminal NousTokenError
- slow_down backs off (interval bump) then resumes polling to success
- authorization_pending until deadline raises a timed-out error
- refresh omitting a new refresh_token keeps the previous one (header sent)

* fix(oauth/nous): enforce HTTPS base URL and single-use refresh rotation; docs + tests

Addresses the two CHANGES_REQUESTED blockers on PR lidge-jun#1397:

1. resolvePortalBaseUrl() now hard-validates the full OAuth base URL via
   new URL() and throws BEFORE any fetch is dispatched: rejects non-HTTPS
   schemes, embedded credentials, query strings, and fragments; returns
   only url.origin. Aligns opencodex with Hermes hermes_cli/auth.py
   (_NOUS_PORTAL_ALLOWED_HOSTS, https-only) and prevents the single-use
   refresh token / inference JWT from ever traversing cleartext.

2. parseTokenPayload() no longer falls back to the submitted refresh token.
   A response that omits refresh_token, or returns a replacement equal to
   the submitted token, throws NousTokenError(oauthError:
   'refresh_token_reused') so the next refresh cannot replay a consumed
   credential and trigger session revocation.

Also:
- tests/nous-oauth.test.ts: HTTPS/URL hardening (fetch never reached),
  missing/equal refresh rejection, and NousTokenError.oauthError contract
  on access_denied / expired_token.
- tests/nous-oauth-live.test.ts: opt-in, CI-skipped live verification that
  reads the local refresh token without printing it (lengths only), asserts
  rotation + read-only /v1/models reachability. No provider key is shared.
- docs ru/guides/providers.md: eight OAuth presets, ocx login nous, nous row.

Verified: tsc --noEmit, bun test nous-oauth (17/17), privacy:scan passed,
targeted suite 186/186. Full bun run test in progress.

* fix(oauth/nous): failure-atomic refresh, terminal errors, scope check, redirect guard

Addresses the 10 review points from Wibias on PR lidge-jun#1397:

- #2 Single-use refresh is now failure-atomic. A durable refresh-intent file
  (keyed by a sha256 of the refresh token, never the token in cleartext) is
  written before the refresh request and cleared only after the rotated token
  is obtained. If the server responds but the rotation cannot be persisted, the
  intent is marked 'uncertain' and a later refresh REFUSES to replay the
  possibly-consumed token (NousTokenError refresh_token_reused, terminal) —
  forcing a clean re-auth instead of a session-revoking replay.
- #3 Credential-bearing OAuth requests (device + token) now pass
  redirect: 'error' so custom auth headers cannot follow a cross-origin
  redirect.
- #4 invalid_token (and invalid_grant/revoked/revoked_token) are now terminal
  NousTokenError values that drive re-authentication.
- #5 The returned access-token JWT scope is validated for inference:invoke
  before the credential is treated as usable. An insufficient-scope token is a
  terminal error that STILL surfaces the already-rotated refresh token, so the
  caller can persist it and re-auth without discarding the rotation.
- #6 Live /models test accepts both the OpenAI-style { data: [...] } body and a
  bare array (production contract).
- #7 freeTier is no longer true for the mixed free/paid provider; free models
  are classified at model level (the :free slugs). Parity test updated.
- #8 pollForToken parses the response body once and passes the payload through
  to the error path instead of re-reading a consumed body.
- #9 sleep() now removes its abort listener on both resolve and abort, so
  polling iterations do not accumulate listeners.
- #1 The live test is now non-destructive: it persists the rotated token back
  through mergeAccountCredential (prod path), so the local session stays valid.
- #10 Russian docs already mirror the English source (8 presets, ocx login
  nous, nous table row with device grant + single-use rotation).

No provider API key is shared; privacy:scan passes. Verified: tsc --noEmit,
nous-oauth 21/21, provider-registry-parity + targeted suite 193/193.

* fix(oauth/nous): close the uncertain-outcome window for single-use refresh

Deep re-review (real execution proof) showed the first intent design still
relied on the server to refuse a replay when the rotated token was obtained
but lost before the store persisted it. Harden the contract:

- The refresh-intent file now stays in the 'submitted' state after a
  successful rotation (it previously cleared it). It is only cleared by the
  account store via clearNousRefreshIntent() once mergeAccountCredential
  persists the rotated token.
- Replaying a token whose intent is 'submitted' OR 'uncertain' is refused up
  front (NousTokenError refresh_token_reused, terminal) — never blindly
  replayed, and without depending on the server's reuse detection.
- Network-level failure (server never saw the token) still clears the intent
  so a retry is safe.
- clearNousRefreshIntent is wired into the shared refresh orchestrator
  (src/oauth/index.ts) right after mergeAccountCredential; it is a no-op for
  non-Nous providers (they never write an intent).

Verified by a real execution probe (not just mocks): a rotation that obtains
the rotated token but crashes before persistence now makes the next replay of
the old token refused by the guard, with the intent present on disk.

Tests: nous-oauth 23/23 (adds 'rotated token obtained but not persisted
blocks replay', '200 unparseable body marks uncertain', 'network failure
replayable'); targeted suite 195/195. tsc + privacy:scan clean.

* fix(oauth/nous): fail-closed refresh-intent, hardened IO, shared-classifier terminal

Addresses the remaining CHANGES_REQUESTED findings from Wibias on PR lidge-jun#1397
(head after this: fail-closed end-to-end single-use refresh recovery).

1. Refresh-intent is now FAIL-CLOSED and reuses the repo's hardened config IO:
   - writeRefreshIntent uses atomicWriteFile + hardenConfigDir (owner-only
     0o700 dir) and THROWS on failure instead of swallowing it (refresh is
     refused rather than proceeding blind). readRefreshIntent treats any
     read/parse/permission error as 'uncertain' (replay refused), never as
     absent. clearNousRefreshIntent surfaces non-ENOENT failures.
   - Ambiguous fetch failures (timeout/abort/connection) now mark the intent
     'uncertain' instead of clearing it: dispatch may have occurred, so the
     submitted token must never be replayed.
2. Post-persist cleanup is wired into the correct coordinator
   (refreshGenericAccountWithLock, the actual Nous path) after a successful
   mergeAccountCredential; removed the misplaced call from the xAI path.
3. Shared terminal classifier now honors NousTokenError.terminal (so
   provider-classified invalid_token / insufficient_scope move the account to
   re-authentication instead of staying retryable).
4. Opt-in live test refreshes through the production, generation-aware,
   account-locked coordinator (refreshGenericAccountWithLock) instead of
   calling refreshNousToken + mergeAccountCredential outside the lock.
5. First normal refresh-wiring test now isolates OPENCODEX_HOME so it cannot
   leave durable intent state in the config tree.
6. Embedded-credential URL validation no longer echoes the raw (credential-
   bearing) URL in the thrown error.
7. NousTokenError no longer stores live credentials as an enumerable property;
   only the rotated refresh token is retained, via a non-enumerable getter
   (getRotatedRefresh), so structured logging/serialization cannot leak it.
8. Replay-guard test now proves fetch is never called (not just the error
   shape).
9. Provider docs (ja/ko/zh-cn) updated to 'eight' OAuth presets to match the
   English/Russian sources.

Verified by a real execution probe (not just mocks): rotation obtained but not
persisted -> next replay refused by guard; network failure -> fail-closed
uncertain (not replayable); insufficient_scope error does not leak credentials.

Tests: nous-oauth 23/23 (adds fail-closed network-failure, replay-guard
proves-no-fetch, non-enumerable credentials); targeted suite 195/195.
tsc --noEmit and bun run privacy:scan clean. Kept draft, no maintainer-sponsored.

* fix(oauth/nous): fail-closed refresh-intent schema, atomic HTTP failure, non-terminal local IO

- Validate persisted refresh-intent schema; corrupt/unknown state is treated
  as uncertain (replay refused), never absent. Only ENOENT means no intent.
- Classify HTTP refresh failures atomically: ambiguous 5xx/gateway responses
  leave the submitted token blocked (uncertain); only definitive 4xx client
  rejections clear the intent for a safe retry.
- Surface local durable-write/read/cleanup failures as a non-terminal
  RefreshIntentIOError so the coordinator does not mark a valid credential
  needsReauth for broken local persistence.
- Mark device-flow access_denied/expired_token as terminal consistently.
- Handle non-JSON successful device-code bodies with the clear validation
  error instead of a raw JSON parse leak.
- Redact raw values from malformed base-URL diagnostics.
- Align the opaque-token docstring with the JWT scope gate.
- Synchronize OAuth provider lists across en/ja/ko/ru/zh-cn docs.
- Add regression coverage for all safety contracts.

* test(oauth/nous): cover origin-only base URL normalization

* test(oauth/nous): make intent-write failure tests platform-independent

Planting a file at the intent-directory path made the guard read fail with
ENOTDIR on Linux (treated as uncertain -> terminal) before any write could
fail, so the test could not reach the non-terminal operational-error path.
Force atomicWriteFile to fail via a spy instead, deterministically on every
platform: the pre-dispatch write abort must surface RefreshIntentIOError,
never call fetch, and leave the account valid.

* fix(oauth/nous): fail closed on every ambiguous post-dispatch refresh outcome

A non-2xx response does not prove the single-use refresh token was not
consumed: 429 rate limits, unknown/custom 4xx, and gateway-generated
client-class errors can be returned after the remote side already processed
the token. Previously every 4xx cleared the durable refresh intent, which
made a possibly-consumed RT-A locally replayable.

Now every post-dispatch non-2xx response retains the intent as uncertain
(previously only 5xx did), so the submitted token stays blocked and a later
refresh is rejected before any fetch. The intent is cleared only after the
rotated credential is durably persisted. Pre-dispatch local I/O failures
remain distinct non-terminal operational errors.

Replace the invented 'safe 4xx' test with regressions proving HTTP 429 and
an unknown/custom 4xx both keep the old token blocked and reject a second
attempt before fetch (exactly one token-endpoint call).

* fix(oauth/nous): post-persist intent cleanup is best-effort; docs/live-test/modelDiscovery cleanups

- refreshGenericAccountWithLock: a failure to unlink the old-token refresh-
  intent file after mergeAccountCredential commits the rotation no longer
  fails the refresh or marks the account needsReauth. The stale intent keys
  the old token (no longer stored), so retaining it is safe; the failure is
  logged non-fatally with no credential material.
- Add coordinator-level regressions: the happy path persists RT-B and clears
  the RT-A intent (nousRefreshIntentBlocksReplay(RT-A) === false), and a
  forced cleanup failure still resolves with the fresh access token while the
  stored credential stays RT-B and the account is not marked needsReauth.
- Add the provider-level clear-after-persist regression in nous-oauth.test.ts.
- English providers doc: after a terminal Nous refresh failure, run
  'ocx login nous' to reauthenticate.
- Live test: correct the privacy wording (opt-in; credentials go only to the
  intended Nous endpoints; token values never printed) and parse the live
  catalog defensively so malformed bodies yield an empty list instead of a
  crash.
- Nous registry modelDiscovery: use path 'models' (resolves against
  effectiveBaseUrl to the same canonical /v1/models endpoint).

* docs(providers): sync Nous Portal details across ja/ko/ru/zh-cn

Add the missing ocx login nous command, the full
ous provider table row
(openai-chat adapter, inference endpoint, device-grant login, per-request
inference JWT, live paid/:free discovery, single-use rotated refresh tokens),
and the terminal-refresh reauthentication instruction to each translated
provider guide, matching the English source.

* fix(oauth/nous): preserve rotated RT-B on terminal refresh errors; trim live-test model ids

- refreshGenericAccountWithLock: when a terminal NousTokenError carries an
  already-issued rotated refresh token (e.g. access JWT lacks inference:invoke),
  persist RT-B generation-safely before forcing reauthentication. The unusable
  access token is never persisted as valid (empty placeholder, past expiry);
  RT-A's intent is cleared only after RT-B is durable (best-effort cleanup);
  persistence failure or a superseding concurrent generation never clears RT-A
  intent and never overwrites the newer credential; the account is marked
  needsReauth generation-safely and the caller receives OAuthLoginRequiredError.
- Live catalog test: reject empty/whitespace-only model ids (trim before accept).
- Coordinator regressions: RT-B preservation on insufficient_scope, RT-B
  persistence failure keeps RT-A intent blocking, superseded concurrent
  generation is not overwritten, cleanup failure after RT-B persistence keeps
  RT-B and marks needsReauth.

---------

Co-authored-by: Wibias <37517432+Wibias@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants