@smthrs/journal 1.0.0-rc.6 → 1.0.0-rc.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -31,13 +31,14 @@ not a substring test, so `tokenizer`, `secretary`, `monkey`, and
31
31
  `idempotencyKey` stay intact. Redacting them would destroy data in a permanent
32
32
  row without protecting anything, and `idempotencyKey` in particular is the
33
33
  durable identity an effect boundary replays on. Token counters are kept too: a
34
- non-negative safe integer under `inputTokens`, `outputTokens`,
35
- `cachedInputTokens`, `reasoningTokens`, or `totalTokens` is accounting, not
36
- bearer material.
34
+ non-negative safe integer under any name ending in a plural `tokens`
35
+ (`inputTokens`, `cacheWriteTokens`, `max_tokens`) or under `inputToken`,
36
+ `outputToken`, `cachedInputToken`, `reasoningToken`, or `totalToken` is
37
+ accounting, not bearer material. A bare `token` is redacted even when numeric.
37
38
 
38
39
  **Textual.** `Redaction.defaultRules` scans inside any string for credential
39
- shapes seen in real bug reports: provider keys, bearer tokens, GitHub, AWS,
40
- Slack, and Google credentials, URL passwords, Basic authorization, bare JWTs,
40
+ shapes seen in real bug reports: provider keys, bearer tokens, GitHub, GitLab, npm,
41
+ AWS, Slack (tokens and webhook URLs), and Google credentials, URL passwords, Basic authorization, bare JWTs,
41
42
  PEM private-key blocks, INI/YAML credential assignments such as
42
43
  `SECRET = value` and `password: value`, credential flags such as
43
44
  `--password value`, cookie and session assignments, and embedded JSON
@@ -63,6 +64,9 @@ and a count under a plural `tokens` name (`max_tokens: 4096`) is kept.
63
64
  A credential name that is a URL's user
64
65
  (`https://x-access-token:…@github.com/acme/app.git`) takes only the password
65
66
  before `@`, so the host and path a failed clone is diagnosed by are kept.
67
+ A URL password runs to the last `@` before the host, as a URL parser reads it,
68
+ so `redis://:pa:ss@cache` and `mysql://root:p@ss@db` lose the whole password,
69
+ and a URL with no password (`ssh://git@host`) is unchanged.
66
70
 
67
71
  In a journal row a bare value is one token, and a string or container the text
68
72
  never closes stops at the end of its line: a stray backtick in an agent's
@@ -7,7 +7,11 @@ sidebar:
7
7
 
8
8
  ## Install the package
9
9
 
10
- Not on npm yet; see [Installation](https://github.com/smithersai/smithers/blob/main/packages/smithers/flows/flow/docs/installation.md#use-the-libraries).
10
+ ```bash
11
+ npm install @smthrs/journal@next effect
12
+ ```
13
+
14
+ The `next` tag carries the 1.0 release candidates.
11
15
 
12
16
  The package requires Node.js 26.4.0 or later and ships as both ESM and
13
17
  CommonJS with TypeScript declarations. Its only runtime dependencies are
@@ -17,10 +17,13 @@ redacted.
17
17
 
18
18
  - Node.js 26.4.0 or later, where the built-in SQLite module lives.
19
19
  - An ESM package (`"type": "module"`, because the program ends in a top-level
20
- `await`) with the journal, the database it writes through, and `effect`
21
- installed. See [Installation](./installation.md) for the `effect` range.
20
+ `await`) with the journal, the database it writes through, its Node SQLite
21
+ driver, and `effect` installed. See [Installation](./installation.md) for the
22
+ `effect` range.
22
23
 
23
- Not on npm yet; see [Installation](https://github.com/smithersai/smithers/blob/main/packages/smithers/flows/flow/docs/installation.md#use-the-libraries).
24
+ ```bash
25
+ npm install @smthrs/journal@next @smthrs/database@next @effect/sql-sqlite-node effect
26
+ ```
24
27
 
25
28
  ## Compose the layer
26
29
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@smthrs/journal",
3
3
  "type": "module",
4
- "version": "1.0.0-rc.6",
4
+ "version": "1.0.0-rc.8",
5
5
  "license": "MIT",
6
6
  "smthrs": {
7
7
  "group": "engine"
@@ -226,14 +226,14 @@
226
226
  "tag": "next"
227
227
  },
228
228
  "dependencies": {
229
- "@smthrs/database": "1.0.0-rc.6"
229
+ "@smthrs/database": "1.0.0-rc.8"
230
230
  },
231
231
  "devDependencies": {
232
232
  "@effect/language-service": "0.87.2",
233
233
  "@effect/sql-sqlite-node": "4.0.4",
234
234
  "@effect/vitest": "4.0.4",
235
235
  "@eslint/js": "9.39.5",
236
- "@smthrs/flow": "1.0.0-rc.6",
236
+ "@smthrs/flow": "1.0.0-rc.8",
237
237
  "@types/node": "26.4.1",
238
238
  "@vitest/coverage-v8": "5.0.0",
239
239
  "dprint": "0.57.1",
@@ -254,7 +254,7 @@
254
254
  "effect": "^4.0.4"
255
255
  },
256
256
  "scripts": {
257
- "lint": "eslint src --max-warnings=0 && dprint check",
257
+ "lint": "eslint src examples --max-warnings=0 && dprint check",
258
258
  "format": "dprint fmt",
259
259
  "build": "node scripts/build.mjs",
260
260
  "check": "tsc -b tsconfig.json && tsc -p tsconfig.test.json --noEmit",
package/src/Redaction.ts CHANGED
@@ -779,7 +779,14 @@ export const defaultRules: ReadonlyArray<Rule> = [
779
779
  // of `aaaaaaaa-` cost 11 seconds, on the path every journal write and every
780
780
  // log line takes. A scheme longer than 30 characters is not one this net is
781
781
  // for, and the cap makes each start position cost a constant.
782
- pattern: /\b([a-z][a-z0-9+.-]{0,30}:\/\/[^\s:@/]+):(?!\[REDACTED\]@)[^\s:@/]+@/gi,
782
+ //
783
+ // The username may be empty (`redis://:password@host`), and the password
784
+ // runs to the LAST `@` before the authority ends, as a URL parser reads
785
+ // it: RFC 3986 allows `:` in a password and a raw `@` is common in
786
+ // connection strings. Stopping at the first `:` or `@` left the tail of
787
+ // such a password in the row. Quotes and backslashes end the scan so a
788
+ // URL inside JSON text never swallows the next string.
789
+ pattern: /\b([a-z][a-z0-9+.-]{0,30}:\/\/[^\s:@/]*):(?!\[REDACTED\]@)[^\s/?#"'`\\]*@/gi,
783
790
  replace: "$1:[REDACTED]@"
784
791
  },
785
792
  {
@@ -836,6 +843,23 @@ export const defaultRules: ReadonlyArray<Rule> = [
836
843
  pattern: /\bAIza[0-9A-Za-z_-]{35,}(?![0-9A-Za-z_-])/g,
837
844
  replace: "[REDACTED]"
838
845
  },
846
+ {
847
+ id: "gitlab-token",
848
+ pattern: /\bglpat-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g,
849
+ replace: "[REDACTED]"
850
+ },
851
+ {
852
+ id: "npm-token",
853
+ pattern: /\bnpm_[A-Za-z0-9]{36}(?![A-Za-z0-9])/g,
854
+ replace: "[REDACTED]"
855
+ },
856
+ {
857
+ // The webhook path is the whole credential: anyone holding it can post
858
+ // to the channel. The host stays so a failed delivery is still readable.
859
+ id: "slack-webhook",
860
+ pattern: /(\bhooks\.slack\.com\/services\/)T[A-Z0-9]+\/B[A-Z0-9]+\/[A-Za-z0-9]+/g,
861
+ replace: "$1[REDACTED]"
862
+ },
839
863
  {
840
864
  id: "assignment",
841
865
  // A credential name, `:`, `=` or `=>`, and its whole value: quoted at any
@@ -1056,13 +1080,16 @@ const tokenCounterKeys: ReadonlySet<string> = new Set([
1056
1080
  ])
1057
1081
 
1058
1082
  /**
1059
- * A bare plural `tokens` is a count too: `Envelope.budget.tokens` is the run's
1060
- * token ceiling, and redacting it erased every journaled budget. A singular
1061
- * `token` stays bearer material even when it is numeric.
1083
+ * Any name ending in a plural `tokens` holds a count too, the rule the textual
1084
+ * pass applies in {@link namesCredential}: `Envelope.budget.tokens` is the
1085
+ * run's token ceiling, and `usage.cacheWriteTokens`, `maxTokens`, and
1086
+ * `contextWindowTokens` are accounting; redacting them erased every journaled
1087
+ * budget and cache-write count (#3846). A bare singular `token` stays bearer
1088
+ * material even when it is numeric.
1062
1089
  */
1063
1090
  const isTokenCount = (key: string, value: unknown): boolean =>
1064
1091
  typeof value === "number" && Number.isSafeInteger(value) && value >= 0 &&
1065
- (tokenCounterKeys.has(canonicalKey(key)) || key.toLowerCase() === "tokens")
1092
+ (tokenCounterKeys.has(canonicalKey(key)) || /tokens$/i.test(key))
1066
1093
 
1067
1094
  /**
1068
1095
  * Whether a field name names a credential.