Skip to content

feat(payload)!: switch pipeline wire format from TSV to JSON, v0.3.0 (AGG-116)#6

Closed
anthrax63 wants to merge 1 commit into
agent/full-stack-developer/bb842c36from
agent/full-stack-developer/8cfeea2f
Closed

feat(payload)!: switch pipeline wire format from TSV to JSON, v0.3.0 (AGG-116)#6
anthrax63 wants to merge 1 commit into
agent/full-stack-developer/bb842c36from
agent/full-stack-developer/8cfeea2f

Conversation

@anthrax63

Copy link
Copy Markdown
Member

Summary

DDM's publish_to_session only supports type: json (see ddm/crates/task_executors/io_operation/src/publish_to_session.rs), so the lookup pipeline in aggregion/kz-scoring PR (#27) now emits its result as a JSON array of row objects extracted via single_value: true from a payload_json cell.

This PR replaces the API-side parser to match. Outer /single / /multi JSON contract is unchanged — callers not affected.

  • src/kz_scoring_api/payload.py new — parse_pipeline_result(str) → list[dict]. Empty payload / null / [] all fold to [], which LookupService._shape then maps to null per the v0.2.1 not-found contract.
  • src/kz_scoring_api/tsv.py removed. LookupService._run_one now calls parse_pipeline_result instead of parse_tsv.
  • Defensive _maybe_double_decode — DDM's Extract single_value on a Utf8 cell serializes as a JSON string, and depending on how vaultee-pipelines relays DDM's session.result_json, the payload may arrive wrapped in one extra JSON-string layer. Being defensive on both is cheaper than pinning the interop precisely.
  • Teststest_payload.py replaces test_tsv.py (empty / whitespace / null / [] / single object / array / bare object / double-encoded string / UTF-8 BOM / invalid JSON / unexpected top-level / non-object row); test_lookup.py and test_endpoints.py updated with json.dumps payloads; new coverage for 0-row fast-path ("[]"null) and defensive layer-2 decoding.
  • Bump: 0.2.1 → 0.3.0 (wire format change on the API↔pipeline boundary).

Refs: AGG-116, AGG-113 (parent, provides v0.2.1 null-contract this builds on), AGG-115 (blocker for e2e).

Base: agent/full-stack-developer/bb842c36 (v0.2.1 null-contract, PR #5) — merges cleanly after that lands.

Test plan

  • pytest -q → 59/59 passed (was 50/50 in PR feat(lookup)!: return null for not-found, v0.2.1 (AGG-113) #5; net +9 tests covering the new JSON contract).
  • ruff check src/ tests/ — clean.
  • e2e через vaultee-pipelines — блокировано AGG-115 (DDM Beeline executor не забирает задачи). Прогонится после его починки, вместе с деплоем aggregion/kz-scoring PR #27.
  • After merge — DevOps bumps tag to v0.3.0 в kartel-deploy/argo/overlays/vaultee/kz-scoring-api.yaml, coordinated with the kz-scoring template rollout.

🤖 Generated with Claude Code

The lookup pipeline's `publish_to_session` was `type: file` on a TSV
Arrow file, which DDM does not support — `task_executors/io_operation/
publish_to_session.rs` errors "Publish file in session is not supported".
Only `type: json` is supported, so the pipeline (see aggregion/kz-scoring
AGG-116 PR) now emits its result as a JSON array of row objects and
extracts it via `type: json / extract single_value: true` from a single-
row `payload_json` cell produced by the decrypt task.

This PR replaces the API-side parser to match:

- `src/kz_scoring_api/payload.py` new — `parse_pipeline_result(str) →
  list[dict]`. Empty payload / JSON `null` / `[]` all fold to `[]`
  (which LookupService._shape then maps to `null` per the v0.2.1
  not-found contract, so /single and /multi shapes are unchanged).
- `src/kz_scoring_api/tsv.py` removed. `LookupService._run_one` now
  calls `parse_pipeline_result` instead of `parse_tsv`.
- Defensive `_maybe_double_decode`: DDM's `Extract single_value` on a
  Utf8 cell serializes as a JSON string, and depending on how vaultee-
  pipelines relays DDM's `session.result_json` field, the payload may
  arrive wrapped in one extra JSON-string layer. Being defensive on
  both is cheaper than pinning the interop precisely — a mis-shape
  here would surface as a 500 to the caller, not silent data
  corruption.

Tests

- `tests/test_payload.py` replaces `tests/test_tsv.py`; covers empty /
  whitespace / `null` / `[]` / single object / array / bare object /
  double-encoded string / UTF-8 BOM / invalid JSON / unexpected
  top-level type / non-object row.
- `tests/test_lookup.py` and `tests/test_endpoints.py` — every TSV
  payload replaced with `json.dumps(...)`. New coverage:
  - `test_lookup_iin_only_empty_array_returns_none` — pipeline
    fast-path on 0-row CH-lookup writes "[]", API surfaces as null.
  - `test_lookup_double_encoded_payload_is_unwrapped` — defensive
    layer 2 decoding.

Wire contract change (breaking on the API↔pipeline boundary — outer
`/single` and `/multi` JSON contract is unchanged, callers not
affected). Bump: 0.2.1 → 0.3.0.

Not in scope

- The pipeline templates + decrypt script rewrite lives in the
  aggregion/kz-scoring PR (AGG-116). This PR only touches the client.
- DDM executor fix — AGG-115. e2e cannot be run until the executor
  starts claiming tasks again; unit + integration-mock tests all
  pass here (59/59).

Refs: AGG-116, AGG-113 (parent), AGG-115 (blocker for e2e).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
@anthrax63

Copy link
Copy Markdown
Member Author

Closing — overengineered. Реальная причина UnicodeDecodeError в decrypt_row — отсутствие extract task перед python-таской в 4 lookup-шаблонах (тот же паттерн что мы уже дважды применяли — AGG-95, AGG-99). Wire format менять не нужно; достаточно перед каждым decrypt_row. Отдельный минимальный PR в kz-scoring сделаю сейчас; kz-scoring-api остаётся на v0.2.1 (null contract, PR #5).

@anthrax63 anthrax63 closed this Jul 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant