Skip to content
Draft
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
19 changes: 19 additions & 0 deletions .changes/016-http-client-byte-level-methods.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
group: platform
component: Framework
impact: compatible
category: Added
---

## Summary

`Http.Client` can now send PUT, HEAD, DELETE, POST and PATCH requests with a raw byte body and read the status code and headers of every response, including 4xx and 5xx. Use `Http.Client.sendSecure method request body` for HTTPS services and `Http.Client.Internal.sendRaw` for trusted loopback endpoints. `Http.getSecure` and `getRaw` keep working exactly as before.

HTTPS byte requests load the system certificate store while keeping certificate
and hostname checks and TLS 1.2 or later. Byte-level requests ignore environment
proxies and stop retaining response chunks as soon as the configured size limit
is exceeded, closing the response instead of waiting for the entire body.
The limit is not a fixed process-memory cap; network buffers can contain extra
bytes. The raw internal API remains caller-restricted, not a loopback validator.

Existing applications do not need code changes. To verify locally, run `./dev test "Http.Client" nhcore-test-core` and confirm the `sendRaw` and `sendSecure` examples pass.
12 changes: 12 additions & 0 deletions .changes/021-crypto-sha256-hmac-hex.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
group: platform
component: Framework
impact: compatible
category: Added
---

## Summary

`Crypto` now offers the raw building blocks behind signatures: `Crypto.sha256` (32-byte digest), `Crypto.hmacSha256 key message` (raw HMAC that can be chained, any key length) and `Crypto.toHex` (lowercase hexadecimal). Use them when a protocol tells you exactly which bytes to hash or sign; keep using `Crypto.signWith` and `Crypto.verifyWith` for webhook secrets, which are unchanged.

Existing applications do not need code changes. To verify locally, run `./dev test "Crypto" nhcore-test-core` and confirm the `sha256`, `hmacSha256` and `toHex` examples pass.
29 changes: 26 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,21 @@ jobs:
with:
name: dist-newstyle
path: dist-newstyle
# Tee the direct per-example output: integration criteria whose locators
# live in nhcore-test-core are proven from this report by the criteria
# gate in test-service. Non-hidden filename: upload-artifact skips
# dotfiles by default.
- name: Run nhcore-test-core
run: LOG_LEVEL=Warn nix develop --command cabal test nhcore-test-core
run: |
set -o pipefail
LOG_LEVEL=Warn nix develop --command cabal test nhcore-test-core \
--test-show-details=direct | tee nhcore-test-core.report
- uses: actions/upload-artifact@v7
with:
name: nhcore-test-core-report
path: nhcore-test-core.report
retention-days: 1
if-no-files-found: error

test-auth:
needs: [build]
Expand Down Expand Up @@ -234,7 +247,8 @@ jobs:
run: LOG_LEVEL=Warn nix develop --command cabal test nhcore-test-integration

test-service:
needs: [build]
# test-core supplies its report for the criteria gate below.
needs: [build, test-core]
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
Expand Down Expand Up @@ -277,12 +291,21 @@ jobs:
set -o pipefail
LOG_LEVEL=Warn nix develop --command cabal test nhcore-test-service \
--test-show-details=direct | tee .nhcore-test-service.report
- uses: actions/download-artifact@v8
if: github.event_name == 'pull_request'
with:
name: nhcore-test-core-report
# One report across both suites: a missing core report fails `cat`
# (closed), never a silently service-only gate.
- name: Prove integration criterion selectors and real fixtures
if: github.event_name == 'pull_request'
env:
BASE_REF: ${{ github.base_ref }}
POSTGRES_AVAILABLE: "true"
run: ./dev spec-check --criteria-runtime "origin/${BASE_REF}" --report .nhcore-test-service.report
run: |
set -euo pipefail
cat nhcore-test-core.report .nhcore-test-service.report > nhcore-criteria.report
./dev spec-check --criteria-runtime "origin/${BASE_REF}" --report nhcore-criteria.report

test-hurl:
needs: [build]
Expand Down
4 changes: 2 additions & 2 deletions codemap/.doc-ratchet
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# doc-ratchet: counters may DECREASE only (CI-enforced by check.py).
# Source-derived (platform-independent) — see generate.py.
undocumented_modules: 212
undocumented_exports: 560
undocumented_doctest_modules: 315
undocumented_exports: 545
undocumented_doctest_modules: 314
76 changes: 38 additions & 38 deletions codemap/api-hot.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ codemap/signatures/ · type-directed search: ./dev api "<type>"

## Task

- `yield :: value -> Task w value` <!-- 602 call sites -->
- `yield :: value -> Task w value` <!-- 601 call sites -->
- `ignoreError :: Task err Unit -> Task w Unit` <!-- 242 call sites -->
- `mapError :: (err1 -> err2) -> Task err1 value -> Task err2 value` <!-- 229 call sites -->
- `throw :: err -> Task err w` <!-- 216 call sites -->
Expand Down Expand Up @@ -116,17 +116,17 @@ codemap/signatures/ · type-directed search: ./dev api "<type>"

## Bytes

- `unwrap :: Bytes -> ByteString` <!-- 47 call sites -->
- `fromLegacy :: ByteString -> Bytes` <!-- 30 call sites -->
- `toLazyLegacy :: Bytes -> LazyByteString` <!-- 16 call sites -->
- `length :: Bytes -> Int` <!-- 4 call sites -->
- `unwrap :: Bytes -> ByteString` <!-- 50 call sites -->
- `fromLegacy :: ByteString -> Bytes` <!-- 32 call sites -->
- `toLazyLegacy :: Bytes -> LazyByteString` <!-- 17 call sites -->
- `length :: Bytes -> Int` <!-- 3 call sites -->
- `>>> [1, 2, 3] |> Bytes.pack |> Bytes.length` → `3`
- `pack :: [Word8] -> Bytes` <!-- 3 call sites -->
- `toBase64 :: Bytes -> Bytes` <!-- 3 call sites -->
- `dropEnd :: Int -> Bytes -> Bytes` <!-- 2 call sites -->
- `empty :: Bytes` <!-- 2 call sites -->
- `isSuffixOf :: Bytes -> Bytes -> Bool` <!-- 2 call sites -->
- `splitOnce :: Bytes -> Bytes -> Maybe (Bytes, Bytes)` <!-- 2 call sites -->
- `fromLazyLegacy :: LazyByteString -> Bytes` <!-- 1 call sites -->

## Maybe

Expand Down Expand Up @@ -167,30 +167,30 @@ codemap/signatures/ · type-directed search: ./dev api "<type>"
- `load :: HasParser config => Task Text config` <!-- 1 call sites -->
- `secret :: FieldDef -> FieldDef` <!-- 1 call sites -->

## Uuid

- `generate :: Task w Uuid` <!-- 22 call sites -->
- `toText :: Uuid -> Text` <!-- 10 call sites -->
- `nil :: Uuid` <!-- 6 call sites -->
- `toLegacy :: Uuid -> UUID` <!-- 5 call sites -->
- `fromText :: Text -> Maybe Uuid` <!-- 3 call sites -->
- `generateV5 :: Uuid -> Text -> Uuid` <!-- 3 call sites -->
- `>>> Uuid.fromText "6ba7b810-9dad-11d1-80b4-00c04fd430c8" |> Maybe.getOrDie |> (\ns -> Uuid.generateV5 ns "python.org") |> Uuid.toText` → `"886313e1-3b8a-5372-9b90-0c9aee199e5d"`
- `fromLegacy :: UUID -> Uuid` <!-- 2 call sites -->

## LinkedList

- `map :: (a -> b) -> LinkedList a -> LinkedList b` <!-- 15 call sites -->
- `reverse :: LinkedList a -> LinkedList a` <!-- 7 call sites -->
- `any :: (a -> Bool) -> LinkedList a -> Bool` <!-- 6 call sites -->
- `filter :: (a -> Bool) -> LinkedList a -> LinkedList a` <!-- 5 call sites -->
- `reverse :: LinkedList a -> LinkedList a` <!-- 5 call sites -->
- `length :: LinkedList a -> Int` <!-- 3 call sites -->
- `find :: (a -> Bool) -> LinkedList a -> Maybe a` <!-- 2 call sites -->
- `get :: Int -> LinkedList a -> Maybe a` <!-- 2 call sites -->
- `head :: LinkedList a -> Maybe a` <!-- 2 call sites -->
- `all :: (a -> Bool) -> LinkedList a -> Bool` <!-- 1 call sites -->
- `concat :: LinkedList (LinkedList a) -> LinkedList a` <!-- 1 call sites -->

## Uuid

- `generate :: Task w Uuid` <!-- 22 call sites -->
- `toText :: Uuid -> Text` <!-- 10 call sites -->
- `nil :: Uuid` <!-- 6 call sites -->
- `toLegacy :: Uuid -> UUID` <!-- 5 call sites -->
- `fromText :: Text -> Maybe Uuid` <!-- 3 call sites -->
- `generateV5 :: Uuid -> Text -> Uuid` <!-- 3 call sites -->
- `>>> Uuid.fromText "6ba7b810-9dad-11d1-80b4-00c04fd430c8" |> Maybe.getOrDie |> (\ns -> Uuid.generateV5 ns "python.org") |> Uuid.toText` → `"886313e1-3b8a-5372-9b90-0c9aee199e5d"`
- `fromLegacy :: UUID -> Uuid` <!-- 2 call sites -->

## AsyncTask

- `sleep :: Int -> Task w Unit` <!-- 18 call sites -->
Expand Down Expand Up @@ -237,6 +237,19 @@ codemap/signatures/ · type-directed search: ./dev api "<type>"
- `selectEventBatch :: Var Int64 -> Maybe RelativePosition -> Maybe ReadDirection -> Maybe (Array EntityName) -> Task PostgresStoreError (Session (Array PostgresEventRecord))` <!-- 1 call sites -->
- `selectEventByGlobalPositionSession :: Int64 -> Session (Maybe PostgresEventRecord)` <!-- 1 call sites -->

## Http.Client

- `request :: Request` <!-- 6 call sites -->
- `withUrl :: Text -> Request -> Request` <!-- 6 call sites -->
- `withTimeout :: Int -> Request -> Request` <!-- 5 call sites -->
- `get :: FromJSON response => Request -> Task Error (Response response)` <!-- 3 call sites -->
- `postForm :: FromJSON response => Request -> Array (Text, Text) -> Task Error (Response response)` <!-- 3 call sites -->
- `addHeader :: Text -> Text -> Request -> Request` <!-- 2 call sites -->
- `patch :: (FromJSON response, ToJSON requestBody) => Request -> requestBody -> Task Error (Response response)` <!-- 2 call sites -->
- `post :: (FromJSON response, ToJSON requestBody) => Request -> requestBody -> Task Error (Response response)` <!-- 2 call sites -->
- `put :: (FromJSON response, ToJSON requestBody) => Request -> requestBody -> Task Error (Response response)` <!-- 2 call sites -->
- `applyMethodAndBody :: Method -> Bytes -> Request -> Request` <!-- 1 call sites -->

## Stream

- `fromArray :: Array value -> Task error (Stream value)` <!-- 7 call sites -->
Expand All @@ -254,19 +267,6 @@ codemap/signatures/ · type-directed search: ./dev api "<type>"
- `fromEpochSeconds :: Int64 -> DateTime` <!-- 6 call sites -->
- `addSeconds :: Int64 -> DateTime -> DateTime` <!-- 1 call sites -->

## Http.Client

- `request :: Request` <!-- 6 call sites -->
- `withUrl :: Text -> Request -> Request` <!-- 6 call sites -->
- `withTimeout :: Int -> Request -> Request` <!-- 5 call sites -->
- `get :: FromJSON response => Request -> Task Error (Response response)` <!-- 3 call sites -->
- `postForm :: FromJSON response => Request -> Array (Text, Text) -> Task Error (Response response)` <!-- 3 call sites -->
- `addHeader :: Text -> Text -> Request -> Request` <!-- 2 call sites -->
- `patch :: (FromJSON response, ToJSON requestBody) => Request -> requestBody -> Task Error (Response response)` <!-- 2 call sites -->
- `post :: (FromJSON response, ToJSON requestBody) => Request -> requestBody -> Task Error (Response response)` <!-- 2 call sites -->
- `put :: (FromJSON response, ToJSON requestBody) => Request -> requestBody -> Task Error (Response response)` <!-- 2 call sites -->
- `delete :: FromJSON response => Request -> Task Error (Response response)` <!-- 1 call sites -->

## Service.Transport.Mcp.JsonRpc

- `errorResponse :: Maybe Value -> JsonRpcError -> JsonRpcResponse` <!-- 12 call sites -->
Expand Down Expand Up @@ -295,29 +295,29 @@ when WRITING TESTS; they are not feature-code frequency signal.
## Task

- `mapError :: (err1 -> err2) -> Task err1 value -> Task err2 value` <!-- 828 test call sites -->
- `yield :: value -> Task w value` <!-- 483 test call sites -->
- `asResult :: Task err value -> Task err2 (Result err value)` <!-- 286 test call sites -->
- `yield :: value -> Task w value` <!-- 484 test call sites -->
- `asResult :: Task err value -> Task err2 (Result err value)` <!-- 294 test call sites -->
- `mapArray :: (element -> Task err output) -> Array element -> Task err (Array output)` <!-- 127 test call sites -->
- `throw :: err -> Task err w` <!-- 124 test call sites -->
- `fromIO :: IO value -> Task w value` <!-- 103 test call sites -->
- `andThen :: (input -> Task err output) -> Task err input -> Task err output` <!-- 92 test call sites -->
- `fromIO :: IO value -> Task w value` <!-- 90 test call sites -->
- `map :: (input -> output) -> Task err input -> Task err output` <!-- 84 test call sites -->

## Array

- `length :: Array a -> Int` <!-- 276 test call sites -->
- `empty :: Array a` <!-- 231 test call sites -->
- `fromLinkedList :: LinkedList a -> Array a` <!-- 221 test call sites -->
- `map :: (a -> b) -> Array a -> Array b` <!-- 138 test call sites -->
- `fromLinkedList :: LinkedList a -> Array a` <!-- 222 test call sites -->
- `map :: (a -> b) -> Array a -> Array b` <!-- 139 test call sites -->
- `get :: Int -> Array a -> Maybe a` <!-- 89 test call sites -->
- `wrap :: a -> Array a` <!-- 60 test call sites -->
- `initialize :: Int -> (Int -> a) -> Array a` <!-- 47 test call sites -->
- `contains :: Eq value => value -> Array value -> Bool` <!-- 41 test call sites -->

## Text

- `contains :: Text -> Text -> Bool` <!-- 450 test call sites -->
- `toBytes :: Text -> Bytes` <!-- 98 test call sites -->
- `contains :: Text -> Text -> Bool` <!-- 451 test call sites -->
- `toBytes :: Text -> Bytes` <!-- 110 test call sites -->
- `fromLinkedList :: LinkedList Char -> Text` <!-- 58 test call sites -->
- `length :: Text -> Int` <!-- 41 test call sites -->
- `repeat :: Int -> Text -> Text` <!-- 35 test call sites -->
Expand Down
14 changes: 13 additions & 1 deletion codemap/phrasebook.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,18 @@ Ok "hello"
True
```

### Crypto

```haskell
>>> Bytes.pack [0, 15, 255] |> Crypto.toHex
"000fff"
```

```haskell
>>> Text.toBytes "abc" |> Crypto.sha256 |> Crypto.toHex
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
```

### Float

```haskell
Expand Down Expand Up @@ -270,4 +282,4 @@ True

---

*37 example sessions · modules with ZERO doctests: 315 (the documentation backlog — see codemap/.doc-ratchet)*
*39 example sessions · modules with ZERO doctests: 314 (the documentation backlog — see codemap/.doc-ratchet)*
16 changes: 16 additions & 0 deletions codemap/signatures/nhcore-core.txt
Original file line number Diff line number Diff line change
Expand Up @@ -577,9 +577,12 @@ data HmacKey
generateHmacKey :: Task err HmacKey
hmacKeyFromBytes :: Bytes -> Result Text HmacKey
hmacKeyFromText :: Text -> Result Text HmacKey
hmacSha256 :: Bytes -> Bytes -> Bytes
instance GHC.Classes.Eq Crypto.HmacKey
instance GHC.Show.Show Crypto.HmacKey
sha256 :: Bytes -> Bytes
signWith :: HmacKey -> Bytes -> Text
toHex :: Bytes -> Text
verifyWith :: HmacKey -> Text -> Bytes -> Bool

module DateTime
Expand Down Expand Up @@ -726,8 +729,14 @@ unchanged :: value -> value
module Http

module Http.Client
Delete :: Method
Error :: Text -> Error
Get :: Method
Head :: Method
InvalidUrl :: Text -> Error
Patch :: Method
Post :: Method
Put :: Method
Request :: Maybe Text -> Map Text Text -> Maybe Int -> Int -> !Maybe Int -> Request
Response :: Int -> Array (Text, Text) -> body -> Response body
ResponseTooLarge :: Int -> Error
Expand All @@ -740,32 +749,39 @@ ResponseTooLarge :: Int -> Error
[timeoutSeconds] :: Request -> Maybe Int
[url] :: Request -> Maybe Text
addHeader :: Text -> Text -> Request -> Request
applyMethodAndBody :: Method -> Bytes -> Request -> Request
checkContentLengthHeaderLimit :: Maybe Int -> [(CI ByteString, ByteString)] -> Maybe Int
data Error
data Method
data Request
data Response body
delete :: FromJSON response => Request -> Task Error (Response response)
get :: FromJSON response => Request -> Task Error (Response response)
getSecure :: Request -> Task Error (Response Bytes)
instance Data.Default.Internal.Default Http.Client.Request
instance GHC.Classes.Eq Http.Client.Method
instance GHC.Generics.Generic (Http.Client.Response body)
instance GHC.Show.Show Http.Client.Error
instance GHC.Show.Show Http.Client.Method
instance GHC.Show.Show Http.Client.Request
instance GHC.Show.Show body => GHC.Show.Show (Http.Client.Response body)
methodName :: Method -> Text
patch :: (FromJSON response, ToJSON requestBody) => Request -> requestBody -> Task Error (Response response)
post :: (FromJSON response, ToJSON requestBody) => Request -> requestBody -> Task Error (Response response)
postForm :: FromJSON response => Request -> Array (Text, Text) -> Task Error (Response response)
postRaw :: FromJSON response => Request -> Text -> Text -> Task Error (Response response)
put :: (FromJSON response, ToJSON requestBody) => Request -> requestBody -> Task Error (Response response)
request :: Request
secureTlsSupportedVersions :: [Version]
sendSecure :: Method -> Request -> Bytes -> Task Error (Response Bytes)
withMaxResponseSize :: Int -> Request -> Request
withRedirects :: Int -> Request -> Request
withTimeout :: Int -> Request -> Request
withUrl :: Text -> Request -> Request

module Http.Client.Internal
getRaw :: Request -> Task Error (Response Bytes)
sendRaw :: Method -> Request -> Bytes -> Task Error (Response Bytes)

module IO
andThen :: (a -> IO b) -> IO a -> IO b
Expand Down
Loading
Loading