ocaml http/1, http/2 and websocket client and server library
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

chore: close web framework building blocks epic

+6
+6
.beads/issues.jsonl
··· 2 2 {"id":"hcs-0y4","title":"Implement compression middleware with gzip and zstd support","description":"","status":"closed","priority":1,"issue_type":"feature","created_at":"2026-01-01T18:44:00.161388436+01:00","updated_at":"2026-01-01T18:53:17.947904901+01:00","closed_at":"2026-01-01T18:53:17.947904901+01:00"} 3 3 {"id":"hcs-0zq","title":"Testing Infrastructure and Compliance","description":"Set up testing infrastructure including unit tests, integration tests, and HTTP compliance test suites for both client and server implementations.","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-12-29T14:35:43.905080385+01:00","updated_at":"2025-12-29T17:57:31.987947689+01:00","closed_at":"2025-12-29T17:57:31.987947689+01:00","dependencies":[{"issue_id":"hcs-0zq","depends_on_id":"hcs-zba","type":"parent-child","created_at":"2025-12-29T14:35:55.080432801+01:00","created_by":"gdiazlo"}]} 4 4 {"id":"hcs-1h7","title":"Create Go WebSocket benchmark server","description":"Go server using gorilla/websocket or nhooyr.io/websocket. Same interface as HCS: accept connections, keep alive, report count.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-30T09:59:59.144195314+01:00","updated_at":"2025-12-30T10:08:50.856707182+01:00","closed_at":"2025-12-30T10:08:50.856707182+01:00","dependencies":[{"issue_id":"hcs-1h7","depends_on_id":"hcs-jk8","type":"parent-child","created_at":"2025-12-30T10:00:30.601603816+01:00","created_by":"gdiazlo"}]} 5 + {"id":"hcs-1op","title":"Web framework building blocks","description":"Implement 5 core features to make HCS a minimal but complete web framework: Sessions, Tokens, Content Negotiation, Endpoint, and PubSub/Channels. Focus on minimal allocations, functional composition, and simplicity over feature parity with Phoenix.","status":"closed","priority":1,"issue_type":"epic","created_at":"2026-01-01T20:46:34.289608444+01:00","updated_at":"2026-01-01T21:36:15.141649231+01:00","closed_at":"2026-01-01T21:36:15.141649231+01:00"} 5 6 {"id":"hcs-1uy","title":"HTTP/2 Specific Features","description":"Implement H2 module with server push, stream priority, and HTTP/2 detection.","status":"closed","priority":3,"issue_type":"epic","created_at":"2025-12-29T14:25:37.55760677+01:00","updated_at":"2025-12-29T17:40:54.000014455+01:00","closed_at":"2025-12-29T17:40:54.000014455+01:00","dependencies":[{"issue_id":"hcs-1uy","depends_on_id":"hcs-zba","type":"parent-child","created_at":"2025-12-29T14:26:20.914923161+01:00","created_by":"gdiazlo"}]} 6 7 {"id":"hcs-1vt","title":"Implement HTTP/2 server (Eio)","description":"Implement HTTP/2 server for Eio in hcs-eio/h2_server.ml:\n\n```ocaml\nval handle_connection :\n flow:Eio.Flow.two_way -\u003e\n clock:Eio.Time.clock -\u003e\n config:Server.config -\u003e\n handler:(request -\u003e (response, error) result) -\u003e\n unit\n```\n\nFeatures:\n- HPACK header compression\n- Stream multiplexing (concurrent requests)\n- Flow control\n- Server push support\n- GOAWAY for graceful shutdown\n- Priority handling\n\nDepends on hpack package.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-29T14:33:25.282940788+01:00","updated_at":"2025-12-29T16:00:42.340368477+01:00","closed_at":"2025-12-29T16:00:42.340368477+01:00","dependencies":[{"issue_id":"hcs-1vt","depends_on_id":"hcs-rw6","type":"parent-child","created_at":"2025-12-29T14:33:42.729783837+01:00","created_by":"gdiazlo"}]} 7 8 {"id":"hcs-23f","title":"Implement synchronous Stream module (core)","description":"Implement the synchronous Stream module in hcs-core/stream.ml:\n\nProducers:\n- empty, singleton, of_list, of_seq\n- unfold : ('s -\u003e ('a * 's) option) -\u003e 's -\u003e 'a t\n\nTransformers:\n- map, filter, filter_map\n- take, drop, chunks\n\nConsumers:\n- fold, iter, drain\n- to_string (for Cstruct.t streams)\n\nCombinators:\n- concat, zip\n\nThis is the pure, synchronous implementation. Runtime-specific async streams will wrap this.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-29T14:28:01.435958367+01:00","updated_at":"2025-12-29T14:51:05.189865738+01:00","closed_at":"2025-12-29T14:51:05.189865738+01:00","labels":["core"],"dependencies":[{"issue_id":"hcs-23f","depends_on_id":"hcs-czm","type":"parent-child","created_at":"2025-12-29T14:28:16.036236675+01:00","created_by":"gdiazlo"}]} ··· 35 36 {"id":"hcs-ag6","title":"Implement H2 module (HTTP/2 specific features)","description":"Implement HTTP/2 specific features in hcs-eio/h2.ml:\n\n```ocaml\n(* Server push *)\nval push : request -\u003e Uri.t -\u003e (unit, error) result\n\n(* Stream priority *)\ntype priority = {\n dependency : int32;\n weight : int; (* 1-256 *)\n exclusive : bool;\n}\n\nval set_priority : priority -\u003e (unit, error) result\n\n(* Check protocol *)\nval is_h2 : request -\u003e bool\n```\n\nLower priority - can be added after basic HTTP/2 works.","status":"closed","priority":3,"issue_type":"task","created_at":"2025-12-29T14:34:36.59579057+01:00","updated_at":"2025-12-29T17:39:24.986755909+01:00","closed_at":"2025-12-29T17:39:24.986755909+01:00","dependencies":[{"issue_id":"hcs-ag6","depends_on_id":"hcs-1uy","type":"parent-child","created_at":"2025-12-29T14:34:53.890566238+01:00","created_by":"gdiazlo"},{"issue_id":"hcs-ag6","depends_on_id":"hcs-d5s","type":"blocks","created_at":"2025-12-29T14:34:58.997623679+01:00","created_by":"gdiazlo"}]} 36 37 {"id":"hcs-ajr","title":"Compare HCS performance against external tools (wrk, hey)","description":"Validate HCS benchmark results by comparing against established tools:\n- Use wrk or hey to benchmark HCS server\n- Compare results with HCS benchmark client\n- Document any discrepancies\n- This validates both our server performance and benchmark accuracy\n\nOptional: Add script to run comparison benchmarks.","status":"closed","priority":3,"issue_type":"task","created_at":"2025-12-29T18:04:05.806924896+01:00","updated_at":"2025-12-29T18:18:13.778810608+01:00","closed_at":"2025-12-29T18:18:13.778810608+01:00","dependencies":[{"issue_id":"hcs-ajr","depends_on_id":"hcs-jtz","type":"parent-child","created_at":"2025-12-29T18:04:25.893845517+01:00","created_by":"gdiazlo"},{"issue_id":"hcs-ajr","depends_on_id":"hcs-4w8","type":"blocks","created_at":"2025-12-29T18:04:39.385790421+01:00","created_by":"gdiazlo"}]} 37 38 {"id":"hcs-ax6","title":"Implement With_codec functor","description":"Implement the With_codec functor in hcs-core/codec.ml:\n\n```ocaml\nmodule With_codec (C : CODEC) : sig\n val encode_body : 'a C.encoder -\u003e 'a -\u003e (body, error) result\n val decode_body : 'a C.decoder -\u003e body -\u003e ('a, error) result\n\n (* Request helpers *)\n val set_body : 'a C.encoder -\u003e 'a -\u003e request -\u003e (request, error) result\n\n (* Response helpers *) \n val read_body : 'a C.decoder -\u003e response -\u003e ('a, error) result\n val make_response : ?status:status -\u003e 'a C.encoder -\u003e 'a -\u003e (response, error) result\nend\n```\n\nThe functor should:\n- Handle body type variants (Empty, Fixed, Stream, File)\n- Set appropriate Content-Type header from C.content_type\n- Convert between Cstruct.t and body types\n- Propagate codec errors as Codec_error\n\nPure OCaml, depends on types.ml and error.ml.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-29T14:28:47.94509919+01:00","updated_at":"2025-12-29T17:04:35.621002337+01:00","closed_at":"2025-12-29T17:04:35.621002337+01:00","labels":["core"],"dependencies":[{"issue_id":"hcs-ax6","depends_on_id":"hcs-m4r","type":"parent-child","created_at":"2025-12-29T14:29:00.595372107+01:00","created_by":"gdiazlo"},{"issue_id":"hcs-ax6","depends_on_id":"hcs-pwc","type":"blocks","created_at":"2025-12-29T14:29:02.656963585+01:00","created_by":"gdiazlo"}]} 39 + {"id":"hcs-b83","title":"Implement Endpoint module","description":"Clean application bootstrap tying router, plugs, and server together.\n\n## Design\n\n### Core API\n```ocaml\nmodule Endpoint : sig\n type t\n type config = {\n port: int;\n bind: string;\n domains: int;\n secret_key_base: string;\n (* TLS, HTTP version, etc. *)\n }\n \n val create : config -\u003e t\n val plug : t -\u003e Plug.t -\u003e t\n val router : t -\u003e Router.t -\u003e t\n val start : t -\u003e env:Eio.Stdenv.t -\u003e unit\nend\n```\n\n### Usage\n```ocaml\nlet () =\n Endpoint.create config\n |\u003e Endpoint.plug (Plug.Logger.create ())\n |\u003e Endpoint.plug (Plug.Session.create ~store ())\n |\u003e Endpoint.plug (Plug.Csrf.create ())\n |\u003e Endpoint.router my_router\n |\u003e Endpoint.start ~env\n```\n\n### Features\n1. **Plug pipeline** - Ordered list of plugs applied to all requests\n2. **Router integration** - Final handler from router\n3. **Error handling** - Catch-all error responses\n4. **Graceful shutdown** - Handle SIGTERM/SIGINT\n5. **Health check** - Built-in /_health endpoint (optional)\n\n### Implementation\n- Composes plugs at startup (no runtime list traversal)\n- Passes config to plugs that need it (session secret, etc.)\n- Single Server.start call with composed handler\n\n### Performance\n- All composition happens at startup\n- Handler is a single function, no dynamic dispatch\n- Zero per-request allocation for endpoint logic","status":"closed","priority":1,"issue_type":"feature","created_at":"2026-01-01T20:47:23.386314333+01:00","updated_at":"2026-01-01T21:30:28.400524063+01:00","closed_at":"2026-01-01T21:30:28.400524063+01:00","dependencies":[{"issue_id":"hcs-b83","depends_on_id":"hcs-1op","type":"parent-child","created_at":"2026-01-01T20:48:06.720271707+01:00","created_by":"gdiazlo"},{"issue_id":"hcs-b83","depends_on_id":"hcs-d0n","type":"blocks","created_at":"2026-01-01T20:48:16.796430672+01:00","created_by":"gdiazlo"},{"issue_id":"hcs-b83","depends_on_id":"hcs-oqc","type":"blocks","created_at":"2026-01-01T20:48:21.832567305+01:00","created_by":"gdiazlo"}]} 38 40 {"id":"hcs-bea","title":"Implement core middleware combinators","description":"Implement middleware composition in hcs-core/middleware.ml:\n\n```ocaml\ntype middleware = (request -\u003e (response, error) result) -\u003e request -\u003e (response, error) result\n\n(* Composition *)\nval ( @\u003e ) : middleware -\u003e middleware -\u003e middleware\nval compose : middleware list -\u003e middleware\nval identity : middleware\n\n(* Pure middleware (no IO needed) *)\nval default_headers : (string * string) list -\u003e middleware\nval catch_errors : (error -\u003e response) -\u003e middleware\nval body_limit : int64 -\u003e middleware\nval request_id : ?header:string -\u003e ?generator:(unit -\u003e string) -\u003e unit -\u003e middleware\n\n(* Security headers *)\nval security_headers : middleware\nval cors :\n ?origins:[ `All | `List of string list ] -\u003e\n ?methods:method_ list -\u003e\n ?headers:string list -\u003e\n ?max_age:int -\u003e\n ?credentials:bool -\u003e\n unit -\u003e\n middleware\n\n(* Auth - validation functions are pure *)\nval basic_auth : realm:string -\u003e validate:(user:string -\u003e pass:string -\u003e bool) -\u003e middleware\nval bearer_auth : validate:(token:string -\u003e bool) -\u003e middleware\n```\n\nPure OCaml.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-29T14:34:27.663177003+01:00","updated_at":"2025-12-29T15:18:06.262737908+01:00","closed_at":"2025-12-29T15:18:06.262737908+01:00","dependencies":[{"issue_id":"hcs-bea","depends_on_id":"hcs-3ww","type":"parent-child","created_at":"2025-12-29T14:34:50.365242865+01:00","created_by":"gdiazlo"}]} 39 41 {"id":"hcs-chm","title":"Implement body type","description":"Implement the body type in types.ml:\n- Empty variant\n- Fixed of string variant\n- Stream of (unit -\u003e Cstruct.t option) - pull-based streaming\n- File of string * int64 * int64 (path, offset, length)\n\nNote: The Stream variant uses a function type that is runtime-agnostic. For async streaming, we'll need a runtime-parameterized body type in the IO layer.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-29T14:27:16.560667848+01:00","updated_at":"2025-12-29T14:50:45.579783806+01:00","closed_at":"2025-12-29T14:50:45.579783806+01:00","labels":["core"],"dependencies":[{"issue_id":"hcs-chm","depends_on_id":"hcs-ugs","type":"parent-child","created_at":"2025-12-29T14:27:40.664331169+01:00","created_by":"gdiazlo"}]} 40 42 {"id":"hcs-cks","title":"Implement Headers module","description":"Implement case-insensitive header map in headers.ml:\n- type t (backed by Map with lowercase keys)\n- empty, singleton, add, add_list\n- find, find_all, remove, mem\n- fold, to_list, of_list\n- Consider using a more efficient representation (e.g., sorted list for small headers)\n\nPure OCaml, no runtime dependency.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-29T14:27:12.768404369+01:00","updated_at":"2025-12-29T14:50:44.414829218+01:00","closed_at":"2025-12-29T14:50:44.414829218+01:00","labels":["core"],"dependencies":[{"issue_id":"hcs-cks","depends_on_id":"hcs-ugs","type":"parent-child","created_at":"2025-12-29T14:27:39.839372964+01:00","created_by":"gdiazlo"}]} ··· 45 47 {"id":"hcs-cyb","title":"Implement Tls_config module","description":"Implement TLS configuration in hcs-core/tls_config.ml:\n\n```ocaml\ntype client\ntype server\n\ntype verification =\n | System_certificates\n | Custom_certificates of string list (* PEM file paths *)\n | Fingerprint of string\n | Insecure_no_verify\n\n(* Client config builder - returns config, actual TLS context created by runtime *)\nval client :\n ?verification:verification -\u003e\n ?alpn_protocols:string list -\u003e\n ?hostname:string -\u003e\n unit -\u003e\n client\n\n(* Server config builder *)\nval server :\n cert_file:string -\u003e\n key_file:string -\u003e\n ?alpn_protocols:string list -\u003e\n ?client_auth:[ `None | `Optional | `Required ] -\u003e\n ?ca_file:string -\u003e\n unit -\u003e\n server\n```\n\nThe actual TLS context creation (using tls-eio or tls-lwt) happens in the runtime layer. This module just holds configuration.\n\nPure OCaml, configuration types only.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-29T14:30:31.480806804+01:00","updated_at":"2025-12-29T15:24:40.571579433+01:00","closed_at":"2025-12-29T15:24:40.571579433+01:00","dependencies":[{"issue_id":"hcs-cyb","depends_on_id":"hcs-y9w","type":"parent-child","created_at":"2025-12-29T14:30:46.125231092+01:00","created_by":"gdiazlo"}]} 46 48 {"id":"hcs-cyk","title":"Implement unified Server module (Eio)","description":"Implement unified server API in hcs-eio/server.ml:\n\n```ocaml\ntype t\n\nval create :\n sw:Eio.Switch.t -\u003e\n net:Eio.Net.t -\u003e\n clock:Eio.Time.clock -\u003e\n ?config:config -\u003e\n Router.compiled -\u003e\n t\n\nval run : t -\u003e unit (* Blocks until shutdown *)\nval shutdown : ?timeout:float -\u003e t -\u003e unit\nval listening_on : t -\u003e (string * int)\nval connection_count : t -\u003e int\n```\n\nFeatures:\n- Accept connections, spawn fibers\n- Protocol detection (ALPN for TLS, prior knowledge)\n- Dispatch to H1 or H2 handler\n- Connection limiting\n- Graceful shutdown with drain timeout\n- TLS support","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-29T14:33:29.304626659+01:00","updated_at":"2025-12-29T17:00:22.652965066+01:00","closed_at":"2025-12-29T17:00:22.652965066+01:00","dependencies":[{"issue_id":"hcs-cyk","depends_on_id":"hcs-rw6","type":"parent-child","created_at":"2025-12-29T14:33:43.591242696+01:00","created_by":"gdiazlo"},{"issue_id":"hcs-cyk","depends_on_id":"hcs-lqi","type":"blocks","created_at":"2025-12-29T14:33:45.174337222+01:00","created_by":"gdiazlo"},{"issue_id":"hcs-cyk","depends_on_id":"hcs-1vt","type":"blocks","created_at":"2025-12-29T14:33:46.071365575+01:00","created_by":"gdiazlo"},{"issue_id":"hcs-cyk","depends_on_id":"hcs-sny","type":"blocks","created_at":"2025-12-29T14:33:46.914541815+01:00","created_by":"gdiazlo"},{"issue_id":"hcs-cyk","depends_on_id":"hcs-2ca","type":"blocks","created_at":"2025-12-29T14:33:47.760104216+01:00","created_by":"gdiazlo"}]} 47 49 {"id":"hcs-czm","title":"Streaming Abstraction","description":"Implement the Stream module with producers, transformers, consumers, and combinators for lazy, backpressure-aware streaming.","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-12-29T14:25:20.968837055+01:00","updated_at":"2025-12-29T17:41:32.374046501+01:00","closed_at":"2025-12-29T17:41:32.374046501+01:00","dependencies":[{"issue_id":"hcs-czm","depends_on_id":"hcs-zba","type":"parent-child","created_at":"2025-12-29T14:25:57.77315075+01:00","created_by":"gdiazlo"}]} 50 + {"id":"hcs-d0n","title":"Implement Sessions plug","description":"Cookie-based session management with pluggable storage backends.\n\n## Design\n\n### Core Types\n```ocaml\ntype session = {\n id: string;\n data: (string * string) list; (* Avoid Map allocation for small sessions *)\n created_at: float;\n modified: bool;\n}\n\ntype store = {\n get: string -\u003e session option;\n put: session -\u003e unit;\n delete: string -\u003e unit;\n}\n```\n\n### Storage Backends\n1. **Memory store** - Hashtbl with optional TTL cleanup (dev/testing)\n2. **Cookie store** - Entire session encrypted in cookie (no server state)\n3. **ETS-like store** - For future distributed sessions\n\n### Plug Implementation\n- `Session.create ~store ~secret ~cookie_name () : Plug.t`\n- Reads session cookie on request, injects into request context\n- Writes session cookie on response if modified\n- Uses AEAD encryption (existing crypto deps) for cookie store\n\n### API for Handlers\n```ocaml\nval get : request -\u003e string -\u003e string option\nval put : request -\u003e string -\u003e string -\u003e request \nval delete : request -\u003e string -\u003e request\nval clear : request -\u003e request\n```\n\n### Performance\n- Lazy session loading (don't decrypt until accessed)\n- Reuse session ID across requests\n- Minimal allocations: parse cookie once, store as list not Map","status":"closed","priority":1,"issue_type":"feature","created_at":"2026-01-01T20:46:56.94254581+01:00","updated_at":"2026-01-01T21:25:06.957911127+01:00","closed_at":"2026-01-01T21:25:06.957911127+01:00","dependencies":[{"issue_id":"hcs-d0n","depends_on_id":"hcs-1op","type":"parent-child","created_at":"2026-01-01T20:47:51.607587044+01:00","created_by":"gdiazlo"}]} 48 51 {"id":"hcs-d3q","title":"Set up Autobahn for WebSocket compliance testing","description":"Integrate Autobahn|Testsuite for WebSocket compliance:\n\n1. Add Autobahn to CI via Docker (crossbario/autobahn-testsuite)\n2. Test both client and server modes:\n\n**Server testing** (Autobahn as client):\n```\ndocker run -it --rm \\\n -v \"${PWD}/reports:/reports\" \\\n crossbario/autobahn-testsuite \\\n wstest -m fuzzingclient -s /config/fuzzingclient.json\n```\n\n**Client testing** (Autobahn as server):\n```\ndocker run -it --rm \\\n -v \"${PWD}/reports:/reports\" \\\n crossbario/autobahn-testsuite \\\n wstest -m fuzzingserver -s /config/fuzzingserver.json\n```\n\nTest cases cover:\n- Framing (text, binary, fragmentation)\n- Ping/Pong\n- Close handshake\n- Reserved bits\n- Opcodes\n- UTF-8 validation\n- Compression (permessage-deflate)\n- Limits and performance\n\nTarget: Pass all non-optional Autobahn test cases","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-29T14:36:18.251422099+01:00","updated_at":"2025-12-29T17:57:13.521624023+01:00","closed_at":"2025-12-29T17:57:13.521624023+01:00","dependencies":[{"issue_id":"hcs-d3q","depends_on_id":"hcs-0zq","type":"parent-child","created_at":"2025-12-29T14:36:48.059495592+01:00","created_by":"gdiazlo"},{"issue_id":"hcs-d3q","depends_on_id":"hcs-nad","type":"blocks","created_at":"2025-12-29T14:36:58.909180795+01:00","created_by":"gdiazlo"}]} 49 52 {"id":"hcs-d5s","title":"Implement HTTP/2 client (Eio)","description":"Implement HTTP/2 client for Eio in hcs-eio/h2_client.ml:\n\n```ocaml\ntype t (* HTTP/2 connection with multiplexed streams *)\n\nval create : flow:Eio.Flow.two_way -\u003e clock:Eio.Time.clock -\u003e config:Client.config -\u003e t\nval request : t -\u003e ?cancel:Cancel.t -\u003e request -\u003e (response, error) result\nval close : t -\u003e unit\n```\n\nFeatures:\n- HPACK header compression\n- Stream multiplexing\n- Flow control per stream and connection\n- SETTINGS frame handling\n- Priority hints (optional)\n- GOAWAY handling\n\nDepends on hpack package.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-29T14:31:23.240487001+01:00","updated_at":"2025-12-29T16:00:40.891022027+01:00","closed_at":"2025-12-29T16:00:40.891022027+01:00","dependencies":[{"issue_id":"hcs-d5s","depends_on_id":"hcs-qnb","type":"parent-child","created_at":"2025-12-29T14:31:50.019661778+01:00","created_by":"gdiazlo"}]} 50 53 {"id":"hcs-d9v","title":"Add retry middleware to middleware_eio.ml","description":"","status":"closed","priority":1,"issue_type":"task","created_at":"2026-01-01T18:15:15.977235935+01:00","updated_at":"2026-01-01T18:16:24.78563205+01:00","closed_at":"2026-01-01T18:16:24.78563205+01:00"} ··· 53 56 {"id":"hcs-dle","title":"Request/Response Helpers","description":"Implement Request and Response helper modules with body handling, status shortcuts, and codec integration.","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-12-29T14:25:33.481216228+01:00","updated_at":"2025-12-29T15:40:53.163093449+01:00","closed_at":"2025-12-29T15:40:53.163093449+01:00","dependencies":[{"issue_id":"hcs-dle","depends_on_id":"hcs-zba","type":"parent-child","created_at":"2025-12-29T14:26:17.959259297+01:00","created_by":"gdiazlo"}]} 54 57 {"id":"hcs-dsf","title":"Set up project structure and dune build","description":"Set up the OCaml project structure with dune:\n\n```\nhcs/\n├── dune-project\n├── hcs-core/ # Pure, runtime-agnostic\n│ ├── dune\n│ ├── types.ml\n│ ├── error.ml\n│ ├── headers.ml\n│ ├── stream.ml # Synchronous stream operations\n│ ├── codec.ml\n│ └── hcs_core.ml # Public API re-exports\n├── hcs-eio/ # Eio runtime\n│ ├── dune\n│ ├── runtime.ml # RUNTIME implementation for Eio\n│ ├── client.ml\n│ ├── server.ml\n│ └── hcs_eio.ml\n└── hcs/ # Convenience package (re-exports hcs-eio)\n ├── dune\n └── hcs.ml\n```\n\nConfigure opam dependencies.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-29T14:27:26.717322855+01:00","updated_at":"2025-12-29T14:50:39.789609025+01:00","closed_at":"2025-12-29T14:50:39.789609025+01:00","labels":["infrastructure"],"dependencies":[{"issue_id":"hcs-dsf","depends_on_id":"hcs-ugs","type":"parent-child","created_at":"2025-12-29T14:27:43.02614445+01:00","created_by":"gdiazlo"}]} 55 58 {"id":"hcs-dzr","title":"Create TechEmpower-style benchmark suite with hyper, fasthttp, and HCS servers","description":"","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-12-30T22:21:05.455078159+01:00","updated_at":"2025-12-30T22:33:15.399922114+01:00","closed_at":"2025-12-30T22:33:15.399922114+01:00"} 59 + {"id":"hcs-ez2","title":"Implement Content Negotiation","description":"Parse Accept headers and select response format.\n\n## Design\n\n### Core Types\n```ocaml\ntype media_type = {\n type_: string; (* e.g., \"application\" *)\n subtype: string; (* e.g., \"json\" *)\n quality: float; (* 0.0 - 1.0 *)\n params: (string * string) list;\n}\n\ntype format = Json | Html | Text | Xml | Custom of string\n```\n\n### Parser\n```ocaml\nval parse_accept : string -\u003e media_type list\n(* Returns sorted by quality, highest first *)\n```\n\n### Negotiation\n```ocaml\nval negotiate : accept:string -\u003e available:format list -\u003e format option\n(* Returns best match or None *)\n\nval negotiate_exn : accept:string -\u003e available:format list -\u003e format\n(* Returns best match or raises Not_acceptable *)\n```\n\n### Plug Integration\n```ocaml\nmodule Plug.Negotiate : sig\n val create : formats:format list -\u003e Plug.t\n (* Sets negotiated format in request, returns 406 if no match *)\nend\n\nval get_format : request -\u003e format option\n```\n\n### Response Helpers\n```ocaml\nval respond_format : format -\u003e body:string -\u003e response\n(* Sets correct Content-Type *)\n\nval respond_negotiate : request -\u003e \n json:(unit -\u003e string) -\u003e \n html:(unit -\u003e string) -\u003e \n response\n(* Lazy evaluation - only runs the selected format *)\n```\n\n### Performance\n- Single-pass parser, no regex\n- Pre-sorted available formats by preference\n- Lazy body generation (no allocation until format selected)","status":"closed","priority":1,"issue_type":"feature","created_at":"2026-01-01T20:47:14.119965918+01:00","updated_at":"2026-01-01T21:01:33.869936139+01:00","closed_at":"2026-01-01T21:01:33.869936139+01:00","dependencies":[{"issue_id":"hcs-ez2","depends_on_id":"hcs-1op","type":"parent-child","created_at":"2026-01-01T20:48:01.682801946+01:00","created_by":"gdiazlo"}]} 56 60 {"id":"hcs-f2r","title":"Skip body reading for bodiless methods (GET/HEAD/DELETE)","description":"In h2_server.ml, skip Buffer.create, Promise.create, and body reading for GET/HEAD/DELETE methods. These have no body but currently allocate a buffer and promise.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-30T08:57:23.954431824+01:00","updated_at":"2025-12-30T09:01:38.714453571+01:00","closed_at":"2025-12-30T09:01:38.714453571+01:00","dependencies":[{"issue_id":"hcs-f2r","depends_on_id":"hcs-cq4","type":"parent-child","created_at":"2025-12-30T08:57:44.799141738+01:00","created_by":"gdiazlo"}]} 57 61 {"id":"hcs-fgd","title":"Logging System","description":"Implement Log module with level, event types, built-in loggers (null, stderr, custom), and event formatting.","status":"closed","priority":2,"issue_type":"epic","created_at":"2025-12-29T14:25:26.517303879+01:00","updated_at":"2025-12-29T17:40:48.765669075+01:00","closed_at":"2025-12-29T17:40:48.765669075+01:00","dependencies":[{"issue_id":"hcs-fgd","depends_on_id":"hcs-zba","type":"parent-child","created_at":"2025-12-29T14:26:08.827592455+01:00","created_by":"gdiazlo"}]} 58 62 {"id":"hcs-fsc","title":"Implement Request helper module","description":"Implement request helpers in hcs-core/request.ml:\n\n```ocaml\nval path : request -\u003e string\nval query : string -\u003e request -\u003e string option\nval query_all : string -\u003e request -\u003e string list\nval header : string -\u003e request -\u003e string option\nval header_all : string -\u003e request -\u003e string list\nval content_type : request -\u003e string option\nval content_length : request -\u003e int64 option\nval is_keep_alive : request -\u003e bool\n\n(* Body consumption - sync versions *)\nval body_string : request -\u003e (string, error) result\nval body_to_cstruct : request -\u003e (Cstruct.t, error) result\n\n(* Form data parsing *)\nval form : request -\u003e ((string * string) list, error) result\n```\n\nPure OCaml + uri package.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-29T14:34:09.842512086+01:00","updated_at":"2025-12-29T15:40:29.136273489+01:00","closed_at":"2025-12-29T15:40:29.136273489+01:00","dependencies":[{"issue_id":"hcs-fsc","depends_on_id":"hcs-dle","type":"parent-child","created_at":"2025-12-29T14:34:47.838799048+01:00","created_by":"gdiazlo"},{"issue_id":"hcs-fsc","depends_on_id":"hcs-lpr","type":"blocks","created_at":"2025-12-29T14:34:54.851316989+01:00","created_by":"gdiazlo"}]} ··· 75 79 {"id":"hcs-kfm","title":"Set up property-based testing for parsers","description":"Use property-based testing (QCheck or Crowbar) for parsers and router:\n\n1. **HTTP/1.1 parser properties:**\n - parse(serialize(request)) = request (roundtrip)\n - parse partial input = Incomplete\n - parse garbage = Error (no crashes)\n - parse valid + garbage = Complete with correct consumed bytes\n\n2. **Router properties:**\n - All registered routes are matchable\n - More specific routes match before less specific\n - No path matches multiple routes (deterministic)\n - Captured params have correct types\n\n3. **Headers properties:**\n - Case-insensitive lookup\n - add then find = Some value\n - remove then find = None\n - of_list(to_list(h)) preserves all values\n\n4. **WebSocket frame properties:**\n - parse(serialize(frame)) = frame\n - Masked frames unmask correctly\n - Fragmented messages reassemble correctly\n\nAdd to dune test configuration with reasonable iteration counts.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-29T14:36:30.160273757+01:00","updated_at":"2025-12-29T17:52:17.694190669+01:00","closed_at":"2025-12-29T17:52:17.694190669+01:00","dependencies":[{"issue_id":"hcs-kfm","depends_on_id":"hcs-0zq","type":"parent-child","created_at":"2025-12-29T14:36:52.71433591+01:00","created_by":"gdiazlo"},{"issue_id":"hcs-kfm","depends_on_id":"hcs-8zr","type":"blocks","created_at":"2025-12-29T14:37:00.796778902+01:00","created_by":"gdiazlo"},{"issue_id":"hcs-kfm","depends_on_id":"hcs-sny","type":"blocks","created_at":"2025-12-29T14:37:01.737277561+01:00","created_by":"gdiazlo"}]} 76 80 {"id":"hcs-kg1","title":"Implement connection pool","description":"Implement connection pooling with runtime abstraction:\n\nhcs-core/pool.ml (data structures):\n```ocaml\ntype key = { host: string; port: int; is_tls: bool }\ntype 'conn entry = { conn: 'conn; last_used: float; created: float }\ntype 'conn t\n\nval create : max_per_host:int -\u003e max_total:int -\u003e 'conn t\nval get : 'conn t -\u003e key -\u003e 'conn entry option\nval put : 'conn t -\u003e key -\u003e 'conn -\u003e now:float -\u003e unit\nval remove : 'conn t -\u003e key -\u003e 'conn -\u003e unit\nval close_idle : 'conn t -\u003e older_than:float -\u003e 'conn list\nval stats : 'conn t -\u003e { active: int; idle: int; total: int }\n```\n\nhcs-eio/pool.ml (Eio-specific):\n```ocaml\nmodule Eio_pool : sig\n type t\n val create : config:Client.config -\u003e t\n val acquire : t -\u003e key -\u003e (Eio.Flow.two_way, error) result\n val release : t -\u003e key -\u003e Eio.Flow.two_way -\u003e unit\n val with_connection : t -\u003e key -\u003e (Eio.Flow.two_way -\u003e 'a) -\u003e ('a, error) result\nend\n```\n\nUse LRU eviction, health checks on reuse.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-29T14:31:11.052080452+01:00","updated_at":"2025-12-29T15:18:04.471112737+01:00","closed_at":"2025-12-29T15:18:04.471112737+01:00","dependencies":[{"issue_id":"hcs-kg1","depends_on_id":"hcs-qnb","type":"parent-child","created_at":"2025-12-29T14:31:44.252516545+01:00","created_by":"gdiazlo"}]} 77 81 {"id":"hcs-l23","title":"HTTP Client DSL","description":"Implement the Http module with request builder DSL for fluent API including headers, query params, body, and codec integration.","status":"closed","priority":2,"issue_type":"epic","created_at":"2025-12-29T14:25:29.533679497+01:00","updated_at":"2025-12-29T17:40:50.224704889+01:00","closed_at":"2025-12-29T17:40:50.224704889+01:00","dependencies":[{"issue_id":"hcs-l23","depends_on_id":"hcs-zba","type":"parent-child","created_at":"2025-12-29T14:26:13.135112824+01:00","created_by":"gdiazlo"}]} 82 + {"id":"hcs-l3v","title":"Implement PubSub and Channels","description":"Real-time pub/sub messaging and structured WebSocket channels.\n\n## Design\n\n### PubSub Core\n```ocaml\nmodule PubSub : sig\n type t\n type topic = string\n type message = string (* Keep simple, user serializes *)\n \n val create : unit -\u003e t\n val subscribe : t -\u003e topic -\u003e (message -\u003e unit) -\u003e subscription\n val unsubscribe : subscription -\u003e unit\n val broadcast : t -\u003e topic -\u003e message -\u003e unit\n val direct : t -\u003e subscriber_id:string -\u003e message -\u003e unit\nend\n```\n\n### Channel (WebSocket abstraction)\n```ocaml\nmodule Channel : sig\n type t\n type event = { topic: string; event: string; payload: string }\n \n val join : t -\u003e topic:string -\u003e params:string -\u003e (unit, string) result\n val leave : t -\u003e topic:string -\u003e unit\n val push : t -\u003e topic:string -\u003e event:string -\u003e payload:string -\u003e unit\n val on : t -\u003e event:string -\u003e (event -\u003e unit) -\u003e unit\nend\n```\n\n### Handler Pattern\n```ocaml\nmodule type Channel_handler = sig\n val join : params:string -\u003e socket:Channel.t -\u003e (unit, string) result\n val handle_in : event:string -\u003e payload:string -\u003e socket:Channel.t -\u003e unit\n val handle_info : message:PubSub.message -\u003e socket:Channel.t -\u003e unit\n val leave : socket:Channel.t -\u003e unit\nend\n```\n\n### Wire Protocol (simple JSON)\n```json\n{\"t\": \"room:123\", \"e\": \"msg\", \"p\": \"...\", \"r\": 1}\n topic event payload ref(for replies)\n```\n\n### Implementation\n1. **PubSub backend** - In-process Hashtbl of topic -\u003e subscriber list\n2. **Channel socket** - Wraps WebSocket with topic subscriptions\n3. **Multiplexing** - Single WebSocket, multiple topic subscriptions\n\n### Performance\n- Topic matching: simple string equality (no wildcards for v1)\n- Message fanout: iterate subscriber list directly\n- No copying: same message string to all subscribers\n- Connection cleanup: unsubscribe all on disconnect","status":"closed","priority":1,"issue_type":"feature","created_at":"2026-01-01T20:47:35.204055877+01:00","updated_at":"2026-01-01T21:34:06.786928112+01:00","closed_at":"2026-01-01T21:34:06.786928112+01:00","dependencies":[{"issue_id":"hcs-l3v","depends_on_id":"hcs-1op","type":"parent-child","created_at":"2026-01-01T20:48:11.757915308+01:00","created_by":"gdiazlo"}]} 78 83 {"id":"hcs-l9p","title":"Create Go HTTP/2 benchmark server (net/http)","description":"Implement a Go HTTP/2 server using net/http with h2c support. Match endpoints: /ping, /bytes/:n, /json. Use golang.org/x/net/http2/h2c for cleartext HTTP/2. Pre-allocate response buffers.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-30T08:21:53.967258404+01:00","updated_at":"2025-12-30T08:48:32.173036222+01:00","closed_at":"2025-12-30T08:48:32.173036222+01:00","dependencies":[{"issue_id":"hcs-l9p","depends_on_id":"hcs-82y","type":"parent-child","created_at":"2025-12-30T08:22:23.072848194+01:00","created_by":"gdiazlo"}]} 79 84 {"id":"hcs-le4","title":"Add streaming compression support for Body_stream responses","description":"Current compress middleware buffers entire response before compressing, making it ineffective for Body_stream responses. Need to:\n\n1. Add gzip_compress_stream that wraps Body_stream with streaming gzip compression using Zlib.flate with Sync_flush\n2. Add decompress_request middleware for incoming Content-Encoding: gzip/zstd bodies\n3. Zstd streaming NOT supported by OCaml bindings - will buffer and compress\n\nTechnical notes:\n- Body_stream has signature: next : unit -\u003e Cstruct.t option\n- Zlib supports streaming via flate with No_flush/Sync_flush/Finish flush modes\n- window_bits=31 for gzip format","status":"closed","priority":1,"issue_type":"feature","created_at":"2026-01-01T19:01:22.570936835+01:00","updated_at":"2026-01-01T19:08:23.118608559+01:00","closed_at":"2026-01-01T19:08:23.118608559+01:00"} 80 85 {"id":"hcs-lhr","title":"WebSocket Support","description":"Implement Ws module with frame types, connection management, send/recv operations, server upgrade handler, and client connect.","status":"closed","priority":2,"issue_type":"epic","created_at":"2025-12-29T14:25:36.29066838+01:00","updated_at":"2025-12-29T16:01:05.272490486+01:00","closed_at":"2025-12-29T16:01:05.272490486+01:00","dependencies":[{"issue_id":"hcs-lhr","depends_on_id":"hcs-zba","type":"parent-child","created_at":"2025-12-29T14:26:20.036599039+01:00","created_by":"gdiazlo"}]} ··· 87 92 {"id":"hcs-nad","title":"Implement WebSocket connection (Eio)","description":"Implement WebSocket for Eio in hcs-eio/websocket.ml:\n\n```ocaml\ntype conn\n\nval is_open : conn -\u003e bool\n\n(* Receive *)\nval recv : conn -\u003e (frame option, error) result\nval recv_timeout : Eio.Time.clock -\u003e float -\u003e conn -\u003e (frame option, error) result\n\n(* Send *)\nval send : conn -\u003e frame -\u003e (unit, error) result\nval close : ?code:int -\u003e ?reason:string -\u003e conn -\u003e (unit, error) result\n\n(* Stream interface *)\nval recv_stream : conn -\u003e frame Stream.t\nval send_stream : conn -\u003e frame Stream.t -\u003e (unit, error) result\n\n(* Server: upgrade handler *)\nval upgrade :\n ?protocols:string list -\u003e\n ?on_close:(int option -\u003e string option -\u003e unit) -\u003e\n (conn -\u003e (unit, error) result) -\u003e\n unit handler\n\n(* Client: connect *)\nval connect :\n sw:Eio.Switch.t -\u003e\n net:Eio.Net.t -\u003e\n clock:Eio.Time.clock -\u003e\n ?tls:Tls_config.client -\u003e\n ?headers:Headers.t -\u003e\n ?protocols:string list -\u003e\n string -\u003e\n (conn, error) result\n```","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-29T14:34:33.686889707+01:00","updated_at":"2025-12-29T16:00:45.086724651+01:00","closed_at":"2025-12-29T16:00:45.086724651+01:00","dependencies":[{"issue_id":"hcs-nad","depends_on_id":"hcs-lhr","type":"parent-child","created_at":"2025-12-29T14:34:52.929848888+01:00","created_by":"gdiazlo"},{"issue_id":"hcs-nad","depends_on_id":"hcs-0ro","type":"blocks","created_at":"2025-12-29T14:34:58.053806421+01:00","created_by":"gdiazlo"}]} 88 93 {"id":"hcs-njk","title":"Run HTTP/2 comparison benchmarks and analyze results","description":"Execute the HTTP/2 benchmarks across all three implementations. Collect req/s, latency (p50/p99), memory usage. Document results in BENCHMARKS.md with analysis of performance characteristics.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-30T08:21:59.417171367+01:00","updated_at":"2025-12-30T08:48:51.035410704+01:00","closed_at":"2025-12-30T08:48:51.035410704+01:00","dependencies":[{"issue_id":"hcs-njk","depends_on_id":"hcs-ddd","type":"blocks","created_at":"2025-12-30T08:22:17.716250574+01:00","created_by":"gdiazlo"},{"issue_id":"hcs-njk","depends_on_id":"hcs-82y","type":"parent-child","created_at":"2025-12-30T08:22:28.307104582+01:00","created_by":"gdiazlo"}]} 89 94 {"id":"hcs-oo5","title":"Benchmark suite: single-CPU comparison of HCS, hyper, fasthttp","description":"","notes":"## Benchmark Results (Dec 31, 2025)\n\n### HCS vs Eio Native (c=1000, 100k requests)\n\n| Domains | HCS run_parallel | Eio run_server | Winner |\n|---------|------------------|----------------|--------|\n| 1 | 142k | 136k | HCS +4% |\n| 2 | 196k | 200k | Eio +2% |\n| 4 | 264k | 259k | HCS +2% |\n| 8 | 259k | 267k | Eio +3% |\n\n**Key Finding**: Both approaches perform similarly. The 2-domain regression previously observed was a measurement artifact.\n\n### HCS vs Fasthttp (c=1000, 100k requests)\n\n| Domains | HCS | Fasthttp | Winner |\n|---------|-----|----------|--------|\n| 1 | 137k | 181k | Fasthttp +32% |\n| 4 | 242k | 186k | **HCS +30%** |\n| 8 | 228k | 204k | **HCS +12%** |\n\n**Key Finding**: HCS scales BETTER than Fasthttp and beats it at 4+ domains!\n\n### Conclusions\n\n1. The nested `Eio_main.run` is NOT a problem - Eio's Domain_manager.run does the same thing internally\n2. SO_REUSEPORT (HCS) vs shared socket (Eio native) perform similarly - no clear winner\n3. HCS multi-core scaling is actually quite good - beats Fasthttp at higher core counts\n4. Earlier \"2-domain regression\" was likely a measurement artifact (port reuse, warmup, etc.)\n\n### Remaining Optimization Opportunities\n\n1. Single-core performance still lags Fasthttp by ~32% - room for improvement in request parsing/response writing\n2. 8-domain shows slight regression from 4-domain for HCS - could investigate GC tuning\n3. Hyper benchmark failed to run - need to fix for comparison","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-12-30T22:41:13.187711389+01:00","updated_at":"2025-12-31T09:33:51.051037492+01:00","closed_at":"2025-12-31T09:33:51.051037492+01:00"} 95 + {"id":"hcs-oqc","title":"Implement signed/encrypted Tokens","description":"Signed and encrypted tokens for auth, password reset, email verification.\n\n## Design\n\n### Core API\n```ocaml\nmodule Token : sig\n type t\n \n val sign : secret:string -\u003e data:string -\u003e max_age:float -\u003e string\n val verify : secret:string -\u003e token:string -\u003e (string, error) result\n \n val encrypt : secret:string -\u003e data:string -\u003e max_age:float -\u003e string \n val decrypt : secret:string -\u003e token:string -\u003e (string, error) result\nend\n```\n\n### Token Format\n```\nsigned: base64(data) . timestamp . base64(hmac)\nencrypted: base64(nonce + ciphertext + tag) . timestamp . base64(hmac)\n```\n\n### Implementation\n- Use existing `digestif` for HMAC-SHA256\n- Use existing `mirage-crypto` for AES-GCM encryption\n- Timestamp encoded as varint for compactness\n- Single allocation for final token string\n\n### Use Cases\n```ocaml\n(* Auth token *)\nlet token = Token.sign ~secret ~data:user_id ~max_age:86400.0\n\n(* Password reset - encrypted so user can't see payload *)\nlet token = Token.encrypt ~secret ~data:user_id ~max_age:3600.0\n\n(* Verify *)\nmatch Token.verify ~secret token with\n| Ok user_id -\u003e ...\n| Error Token.Expired -\u003e ...\n| Error Token.Invalid -\u003e ...\n```\n\n### Performance\n- No JSON parsing, raw bytes\n- Constant-time comparison for signatures\n- Reuse crypto contexts where possible","status":"closed","priority":1,"issue_type":"feature","created_at":"2026-01-01T20:47:03.67375632+01:00","updated_at":"2026-01-01T21:07:48.604332258+01:00","closed_at":"2026-01-01T21:07:48.604332258+01:00","dependencies":[{"issue_id":"hcs-oqc","depends_on_id":"hcs-1op","type":"parent-child","created_at":"2026-01-01T20:47:56.644986614+01:00","created_by":"gdiazlo"}]} 90 96 {"id":"hcs-osg","title":"Create Rust HTTP/2 benchmark server (hyper)","description":"Implement a Rust HTTP/2 server using hyper with h2c (cleartext HTTP/2) support. Match endpoints: /ping, /bytes/:n, /json. Use tokio runtime, pre-allocate response buffers for zero-copy.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-30T08:21:52.194368992+01:00","updated_at":"2025-12-30T08:48:32.166538953+01:00","closed_at":"2025-12-30T08:48:32.166538953+01:00","dependencies":[{"issue_id":"hcs-osg","depends_on_id":"hcs-82y","type":"parent-child","created_at":"2025-12-30T08:22:20.416305094+01:00","created_by":"gdiazlo"}]} 91 97 {"id":"hcs-peu","title":"HCS benchmark client: HTTP/1.1 + HTTP/2 + WebSocket support","description":"Create comprehensive benchmark client supporting all protocols with connection reuse, multiplexing, and WebSocket message throughput.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-31T14:13:46.273123556+01:00","updated_at":"2025-12-31T14:37:50.862757453+01:00","closed_at":"2025-12-31T14:37:50.862757453+01:00","dependencies":[{"issue_id":"hcs-peu","depends_on_id":"hcs-5wp","type":"parent-child","created_at":"2025-12-31T14:14:41.0222415+01:00","created_by":"gdiazlo"}]} 92 98 {"id":"hcs-pnc","title":"Control Flow and Cancellation","description":"Implement Cancel module for cooperative cancellation and Control module for timeout, retry, deadline, and circuit breaker patterns.","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-12-29T14:25:23.840150584+01:00","updated_at":"2025-12-29T15:41:49.225247023+01:00","closed_at":"2025-12-29T15:41:49.225247023+01:00","dependencies":[{"issue_id":"hcs-pnc","depends_on_id":"hcs-zba","type":"parent-child","created_at":"2025-12-29T14:26:03.266144948+01:00","created_by":"gdiazlo"}]}