@tcpcore1/verifier 0.0.0-stage → 0.1.0

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.
Files changed (147) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +465 -2
  3. package/bin/tcpcore.js +35 -0
  4. package/dist/adapters/aws-bedrock-agents.d.ts +46 -0
  5. package/dist/adapters/aws-bedrock-agents.d.ts.map +1 -0
  6. package/dist/adapters/aws-bedrock-agents.js +179 -0
  7. package/dist/adapters/aws-bedrock-agents.js.map +1 -0
  8. package/dist/adapters/claude-code.d.ts +40 -0
  9. package/dist/adapters/claude-code.d.ts.map +1 -0
  10. package/dist/adapters/claude-code.js +159 -0
  11. package/dist/adapters/claude-code.js.map +1 -0
  12. package/dist/adapters/crewai.d.ts +48 -0
  13. package/dist/adapters/crewai.d.ts.map +1 -0
  14. package/dist/adapters/crewai.js +140 -0
  15. package/dist/adapters/crewai.js.map +1 -0
  16. package/dist/adapters/cursor.d.ts +48 -0
  17. package/dist/adapters/cursor.d.ts.map +1 -0
  18. package/dist/adapters/cursor.js +192 -0
  19. package/dist/adapters/cursor.js.map +1 -0
  20. package/dist/adapters/custom-http.d.ts +30 -0
  21. package/dist/adapters/custom-http.d.ts.map +1 -0
  22. package/dist/adapters/custom-http.js +50 -0
  23. package/dist/adapters/custom-http.js.map +1 -0
  24. package/dist/adapters/deepseek-harness.d.ts +38 -0
  25. package/dist/adapters/deepseek-harness.d.ts.map +1 -0
  26. package/dist/adapters/deepseek-harness.js +134 -0
  27. package/dist/adapters/deepseek-harness.js.map +1 -0
  28. package/dist/adapters/google-vertex-agents.d.ts +34 -0
  29. package/dist/adapters/google-vertex-agents.d.ts.map +1 -0
  30. package/dist/adapters/google-vertex-agents.js +149 -0
  31. package/dist/adapters/google-vertex-agents.js.map +1 -0
  32. package/dist/adapters/index.d.ts +21 -0
  33. package/dist/adapters/index.d.ts.map +1 -0
  34. package/dist/adapters/index.js +62 -0
  35. package/dist/adapters/index.js.map +1 -0
  36. package/dist/adapters/langchain.d.ts +62 -0
  37. package/dist/adapters/langchain.d.ts.map +1 -0
  38. package/dist/adapters/langchain.js +199 -0
  39. package/dist/adapters/langchain.js.map +1 -0
  40. package/dist/adapters/langgraph.d.ts +31 -0
  41. package/dist/adapters/langgraph.d.ts.map +1 -0
  42. package/dist/adapters/langgraph.js +58 -0
  43. package/dist/adapters/langgraph.js.map +1 -0
  44. package/dist/adapters/microsoft-autogen.d.ts +34 -0
  45. package/dist/adapters/microsoft-autogen.d.ts.map +1 -0
  46. package/dist/adapters/microsoft-autogen.js +128 -0
  47. package/dist/adapters/microsoft-autogen.js.map +1 -0
  48. package/dist/adapters/openai-assistants.d.ts +34 -0
  49. package/dist/adapters/openai-assistants.d.ts.map +1 -0
  50. package/dist/adapters/openai-assistants.js +126 -0
  51. package/dist/adapters/openai-assistants.js.map +1 -0
  52. package/dist/adapters/tool-call.d.ts +76 -0
  53. package/dist/adapters/tool-call.d.ts.map +1 -0
  54. package/dist/adapters/tool-call.js +167 -0
  55. package/dist/adapters/tool-call.js.map +1 -0
  56. package/dist/adapters/types.d.ts +57 -0
  57. package/dist/adapters/types.d.ts.map +1 -0
  58. package/dist/adapters/types.js +66 -0
  59. package/dist/adapters/types.js.map +1 -0
  60. package/dist/args.d.ts +35 -0
  61. package/dist/args.d.ts.map +1 -0
  62. package/dist/args.js +131 -0
  63. package/dist/args.js.map +1 -0
  64. package/dist/checks/registry.d.ts +61 -0
  65. package/dist/checks/registry.d.ts.map +1 -0
  66. package/dist/checks/registry.js +38 -0
  67. package/dist/checks/registry.js.map +1 -0
  68. package/dist/cli.d.ts +40 -0
  69. package/dist/cli.d.ts.map +1 -0
  70. package/dist/cli.js +236 -0
  71. package/dist/cli.js.map +1 -0
  72. package/dist/errors.d.ts +23 -0
  73. package/dist/errors.d.ts.map +1 -0
  74. package/dist/errors.js +40 -0
  75. package/dist/errors.js.map +1 -0
  76. package/dist/facts/materialise.d.ts +47 -0
  77. package/dist/facts/materialise.d.ts.map +1 -0
  78. package/dist/facts/materialise.js +74 -0
  79. package/dist/facts/materialise.js.map +1 -0
  80. package/dist/facts/parse.d.ts +62 -0
  81. package/dist/facts/parse.d.ts.map +1 -0
  82. package/dist/facts/parse.js +174 -0
  83. package/dist/facts/parse.js.map +1 -0
  84. package/dist/index.d.ts +33 -0
  85. package/dist/index.d.ts.map +1 -0
  86. package/dist/index.js +54 -0
  87. package/dist/index.js.map +1 -0
  88. package/dist/merkle.d.ts +65 -0
  89. package/dist/merkle.d.ts.map +1 -0
  90. package/dist/merkle.js +81 -0
  91. package/dist/merkle.js.map +1 -0
  92. package/dist/render/html.d.ts +16 -0
  93. package/dist/render/html.d.ts.map +1 -0
  94. package/dist/render/html.js +63 -0
  95. package/dist/render/html.js.map +1 -0
  96. package/dist/render/human.d.ts +29 -0
  97. package/dist/render/human.d.ts.map +1 -0
  98. package/dist/render/human.js +105 -0
  99. package/dist/render/human.js.map +1 -0
  100. package/dist/render/json.d.ts +12 -0
  101. package/dist/render/json.d.ts.map +1 -0
  102. package/dist/render/json.js +13 -0
  103. package/dist/render/json.js.map +1 -0
  104. package/dist/session/canonical.d.ts +66 -0
  105. package/dist/session/canonical.d.ts.map +1 -0
  106. package/dist/session/canonical.js +102 -0
  107. package/dist/session/canonical.js.map +1 -0
  108. package/dist/session/parse.d.ts +64 -0
  109. package/dist/session/parse.d.ts.map +1 -0
  110. package/dist/session/parse.js +299 -0
  111. package/dist/session/parse.js.map +1 -0
  112. package/dist/session/types.d.ts +79 -0
  113. package/dist/session/types.d.ts.map +1 -0
  114. package/dist/session/types.js +15 -0
  115. package/dist/session/types.js.map +1 -0
  116. package/dist/vendor/claim-graph/text.d.ts +150 -0
  117. package/dist/vendor/claim-graph/text.d.ts.map +1 -0
  118. package/dist/vendor/claim-graph/text.js +337 -0
  119. package/dist/vendor/claim-graph/text.js.map +1 -0
  120. package/dist/vendor/truth-kernel/merkle.d.ts +246 -0
  121. package/dist/vendor/truth-kernel/merkle.d.ts.map +1 -0
  122. package/dist/vendor/truth-kernel/merkle.js +404 -0
  123. package/dist/vendor/truth-kernel/merkle.js.map +1 -0
  124. package/dist/vendor/truth-kernel/types.d.ts +252 -0
  125. package/dist/vendor/truth-kernel/types.d.ts.map +1 -0
  126. package/dist/vendor/truth-kernel/types.js +158 -0
  127. package/dist/vendor/truth-kernel/types.js.map +1 -0
  128. package/dist/vendor/truth-kernel/verify.d.ts +153 -0
  129. package/dist/vendor/truth-kernel/verify.d.ts.map +1 -0
  130. package/dist/vendor/truth-kernel/verify.js +266 -0
  131. package/dist/vendor/truth-kernel/verify.js.map +1 -0
  132. package/dist/verify/engine.d.ts +92 -0
  133. package/dist/verify/engine.d.ts.map +1 -0
  134. package/dist/verify/engine.js +368 -0
  135. package/dist/verify/engine.js.map +1 -0
  136. package/dist/verify/report.d.ts +87 -0
  137. package/dist/verify/report.d.ts.map +1 -0
  138. package/dist/verify/report.js +11 -0
  139. package/dist/verify/report.js.map +1 -0
  140. package/examples/clean-facts.jsonl +2 -0
  141. package/examples/clean-session.jsonl +3 -0
  142. package/examples/facts.jsonl +3 -0
  143. package/examples/mid-session-retraction.jsonl +3 -0
  144. package/examples/one-fabricated.jsonl +3 -0
  145. package/package.json +55 -4
  146. package/spec/facts-v1.md +84 -0
  147. package/spec/tcpcore-v1.md +143 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 TCPcore
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,466 @@
1
- # Temporary Holding Version
1
+ # @tcpcore1/verifier
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ **Did the agent's claimed actions follow from the facts it was given?**
4
+
5
+ `tcpcore verify` reads a session log and a set of typed facts, and answers that question
6
+ action by action. It prints four numbers, the findings behind them, and a Merkle root over
7
+ the session so a report can be pasted into a review and re-checked later.
8
+
9
+ No account, no server, no model. One command, one report, exit codes a CI job can branch on.
10
+
11
+ ```bash
12
+ npx @tcpcore1/verifier verify examples/one-fabricated.jsonl --facts examples/facts.jsonl
13
+ ```
14
+
15
+ That command runs against a file in this package, and it exits `1` because the second action
16
+ cites a fact that is not in the facts file. There is nothing to prepare, no service to start
17
+ and no capture to make first.
18
+
19
+ > The package is published as `@tcpcore1/verifier` because that is the scope this
20
+ > workspace owns. The bin is called `tcpcore` and does not change with the scope, so
21
+ > `npx @tcpcore1/verifier …` and a globally installed `tcpcore …` are the same command.
22
+ > **No publish has been performed from this repository.** That is the operator's action.
23
+ >
24
+ > The package has **no runtime dependencies**. The parts of the truth kernel it needs (the
25
+ > Merkle tree, the fact types, `AdversarialVerifier`) are copied into `src/vendor/` at build
26
+ > time by `scripts/bundle-kernel.mjs`, because the kernel is not on npm and a published
27
+ > tarball may not declare a `workspace:` dependency. A parity test asserts the copy produces
28
+ > byte-identical Merkle roots to the kernel; see [The Merkle roots](#the-merkle-roots).
29
+
30
+ ---
31
+
32
+ ## The three examples
33
+
34
+ `examples/` ships with the package, so all three of these run from a fresh install. Every
35
+ file's first line carries a `_note` saying the session is synthetic - a real agent that does
36
+ not log grounding facts will show more `ungrounded` than grounded, and that is the tool
37
+ working, not the tool failing.
38
+
39
+ ```bash
40
+ # 1. Nothing to find: two actions, both grounded. Exit 0.
41
+ npx @tcpcore1/verifier verify examples/clean-session.jsonl --facts examples/clean-facts.jsonl
42
+
43
+ # 2. One citation of a fact that does not exist. Exit 1.
44
+ npx @tcpcore1/verifier verify examples/one-fabricated.jsonl --facts examples/facts.jsonl
45
+
46
+ # 3. A fact in force when it was cited, withdrawn later in the same session. Exit 1.
47
+ npx @tcpcore1/verifier verify examples/mid-session-retraction.jsonl --facts examples/facts.jsonl
48
+ ```
49
+
50
+ The second command prints:
51
+
52
+ ```
53
+ tcpcore verify - examples/one-fabricated.jsonl
54
+ 3 fact(s) from examples/facts.jsonl, session example-one-fabricated
55
+
56
+ 2 actions
57
+ 1 grounded
58
+ 1 fabricated <- accusations
59
+ 0 retracted
60
+ 0 ungrounded <- the tool could not adjudicate
61
+
62
+ fabricated:
63
+ a2 billing.apply_discount 2026-11-03T09:00:20.000Z
64
+ cites fact f-annual-discount, which is not in the facts file
65
+ cites: f-annual-discount (declared)
66
+
67
+ merkle (tcpcore.merkle.sha256.v1)
68
+ actions 36bdb02e9721d4350f888f7da5efdb42e711687dce1a28df15ff478ac286c5bd
69
+ citations c433fb55f3c8a8df20e9e82771d14fa8c55278c088f01df29d51eb6f872daca0
70
+
71
+ exit 1 - 1 fabricated: 1 citation(s) of a fact that does not exist
72
+ ```
73
+
74
+ The third prints `1 retracted`, naming `f-refund-window`, and the reason spells out both
75
+ times: _in force at 2026-11-03T09:15:00.000Z and retracted at 2026-11-03T09:30:00.000Z, later
76
+ in the session_. That is the cascade the report exists to make visible.
77
+
78
+ ---
79
+
80
+ ## The two commands a contributor needs
81
+
82
+ `fixtures/` is the test corpus, not part of the published package: every adapter has a committed
83
+ fixture and a test that pins its mapping, and `fixtures/sessions/langchain.tcpcore.jsonl` is the
84
+ golden output of the `langchain` adapter. The `*.tcpcore.jsonl` names are the fixtures' own and
85
+ predate the versioned format name; the format they contain is `tcpcore.session.v1`.
86
+
87
+ ```bash
88
+ # 1. Verify the committed example: seven actions, and every branch of the report.
89
+ npx tsx src/index.ts verify fixtures/sessions/langchain.tcpcore.jsonl \
90
+ --facts fixtures/facts/acme.jsonl
91
+
92
+ # 2. Adapt a framework log into the canonical format, then verify it.
93
+ npx tsx src/index.ts adapt fixtures/claude-code/transcript.jsonl --from claude-code \
94
+ -o /tmp/session.jsonl
95
+ npx tsx src/index.ts verify /tmp/session.jsonl --facts fixtures/facts/acme.jsonl
96
+ ```
97
+
98
+ The output of the first command is:
99
+
100
+ ```
101
+ 7 actions
102
+ 2 grounded
103
+ 2 fabricated <- accusations
104
+ 1 retracted
105
+ 2 ungrounded <- the tool could not adjudicate
106
+ ```
107
+
108
+ then the findings, each naming the fact it contradicts or rests on, then the two roots,
109
+ then `exit 1`.
110
+
111
+ ---
112
+
113
+ ## The headline is four numbers, always
114
+
115
+ ```
116
+ 147 actions
117
+ 132 grounded
118
+ 4 fabricated <- accusations
119
+ 0 retracted
120
+ 11 ungrounded <- the tool could not adjudicate
121
+ ```
122
+
123
+ Not "4 hallucinations found". A developer who reads **4 fabricated** without **11
124
+ ungrounded** beside it will over-trust the report and under-invest in the logging this tool
125
+ needs in order to see more. `ungrounded` means the tool could not adjudicate the action - the
126
+ log did not say which facts it rested on, or it withheld the result. That number is the
127
+ tool's honesty about its own blind spot, and burying it would make the report look more
128
+ certain than it is.
129
+
130
+ The four categories:
131
+
132
+ | Category | Meaning | Fails the run? |
133
+ | ------------ | -------------------------------------------------------------------------------------------------------------- | -------------------- |
134
+ | `grounded` | Every cited fact exists and was in force when the action happened. | no |
135
+ | `fabricated` | A cited fact does not exist in the facts file, **or** the truth kernel contradicted the action's stated claim. | **yes** |
136
+ | `retracted` | Every citation exists, but at least one was withdrawn - before the action, or later in the same session. | **yes** |
137
+ | `ungrounded` | Nothing to adjudicate: no citation and no supplied facts, or the source log redacted the result. | only with `--strict` |
138
+
139
+ `ungrounded` is a fourth bucket rather than a third category of failure, because folding it
140
+ into `grounded` is the "silence is a pass" inversion and folding it into `fabricated` accuses
141
+ the agent of a lie it did not tell.
142
+
143
+ ---
144
+
145
+ ## `tcpcore-v1.jsonl` - the session format (v1)
146
+
147
+ A session file is named for its format version: **`tcpcore-v1.jsonl`**. The older unversioned
148
+ name `tcpcore.jsonl` is accepted too - the format is identified by the `schema` literal inside
149
+ the file (`"tcpcore.session.v1"`), not by the filename, so both names parse identically. The
150
+ versioned name is the one to write today; `spec/tcpcore-v1.md` is the normative document.
151
+
152
+ **A v2 will be added**, as `tcpcore-v2.jsonl` with `"schema": "tcpcore.session.v2"`. The
153
+ verifier will accept v1 and v2 **for one major release**, long enough to migrate without a
154
+ flag day; after that, v1 input is refused by name rather than reinterpreted. Until then there
155
+ is no extension mechanism: a new field is a new version, and `args` is the place for anything
156
+ a tool call carries.
157
+
158
+ One JSON object per line, UTF-8, LF. An optional first line is a header; every other line is
159
+ one action.
160
+
161
+ ```json
162
+ {"type":"session","schema":"tcpcore.session.v1","sessionId":"cc-session-1"}
163
+ {"id":"run_3","action":"billing.read_invoice","ts":"2026-11-03T09:00:10.000Z","args":{"orderId":"O-1","grounding":["f-price-acme"]},"result":{"amount":400},"grounding":["f-price-acme"]}
164
+ ```
165
+
166
+ ### Action fields
167
+
168
+ | Field | Type | Required | Meaning |
169
+ | ----------- | ----------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
170
+ | `action` | string | **yes** | The operation name: `read_file`, `run_tests`, `refund.create`. The claim of having done something. |
171
+ | `ts` | string | **yes** | `YYYY-MM-DDTHH:MM:SS.sssZ`, exactly. The retraction check is a time comparison, so only one spelling is accepted. |
172
+ | `args` | object | **yes** | The structured input. An object, because a tool call has named parameters. |
173
+ | `result` | any JSON | **yes** | What the tool returned. `null`, `""` and `{}` stay distinct: "no output", "empty output" and "empty object" are three different facts. |
174
+ | `grounding` | string[] | no (`[]`) | Fact ids this action rests on. |
175
+ | `id` | string | no (`a1`, `a2`, …) | Stable handle used in the report. Must be unique. |
176
+ | `claim` | object | no | `{subject, predicate, object}`: a structured assertion, adjudicated by the truth kernel. |
177
+ | `agent` | string | no | Which agent or sub-agent produced it. |
178
+ | `turn` | integer ≥ 1 | no | Attribution only; never decisive. |
179
+
180
+ ### Header fields
181
+
182
+ `type` must be `"session"`; `schema` must be `"tcpcore.session.v1"`; `sessionId` and `agent`
183
+ are optional. A header that is not the first line is refused.
184
+
185
+ `_note` is optional and is the one field that carries no semantics: a free-form string, allowed
186
+ on the header and nowhere else. No root commits to the header and no verdict reads it, so it
187
+ cannot change a finding - it is how a shipped example states in the file itself that it is
188
+ synthetic. On an action it is still an unknown key, because every action field is hashed and a
189
+ field outside the hash is a field that can be edited without the root noticing.
190
+
191
+ ### Rules, and why each one is a rule
192
+
193
+ - **Unknown keys are refused**, naming the key and the line. A permissive parser is a silent
194
+ pass at the format layer: it accepts `"grouding"`, drops it, and reports the agent as
195
+ ungrounded because of our typo.
196
+ - **Timestamps must not go backwards.** The retraction check compares `ts` against
197
+ `retractedAt`; a file with no causal order would still produce confident verdicts.
198
+ - **A session with no actions is invalid input (exit 2).** A verification run over nothing is
199
+ not a pass.
200
+ - **A blank line is skipped** - it carries no information, and hand-edited JSONL collects
201
+ trailing newlines.
202
+
203
+ ## `facts-v1.jsonl` - the facts format (v1)
204
+
205
+ ```json
206
+ {
207
+ "id": "f-seat-limit",
208
+ "subject": "acme",
209
+ "predicate": "max_seats",
210
+ "object": "25",
211
+ "ts": "2026-01-05T00:00:00.000Z",
212
+ "source": { "name": "Acme master agreement 2026, schedule 2", "quote": "not more than 25 seats" },
213
+ "factType": "CONSTRAINT"
214
+ }
215
+ ```
216
+
217
+ | Field | Required | Meaning |
218
+ | -------------------------------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
219
+ | `id` | **yes** | What `grounding` cites. Unique across the file. |
220
+ | `subject`, `predicate`, `object` | **yes** | The triple the kernel compares. |
221
+ | `ts` | **yes** | When the fact was recorded, in canonical UTC. |
222
+ | `source` | no | `{name, url?, quote?}`. A bare string is refused: an unquoted, unlinked source is not evidence. |
223
+ | `status` | no | One of the kernel's five. Defaults to **`VERIFIED` when a `source` is present and `ASSERTED` when it is not** - the kernel's own rule, applied rather than invented. |
224
+ | `factType` | no | One of the six. Defaults to `ASSERTION`, the one type that can never make a claim fail. |
225
+ | `retractedAt` | iff `status: RETRACTED` | When it was withdrawn. Required, because "withdrawn before the agent acted" and "withdrawn afterwards" are different findings. |
226
+
227
+ ## How a claim is judged
228
+
229
+ For each action, the citations are the set the agent **declared** (`args.grounding`), or - if
230
+ it declared none - the facts the session had already **supplied** to it, accumulated from
231
+ calls to tools named with `--fact-tool`. Declared citations replace the context rather than
232
+ adding to it: an agent that names its evidence has told us which facts it used, and padding
233
+ that list with everything it ever saw would let one irrelevant fact decide the verdict.
234
+
235
+ Each citation is judged against the facts file:
236
+
237
+ | Condition | Finding |
238
+ | ---------------------------------------- | --------------------------------------------------------------------------------------------------- |
239
+ | the fact id is not in the facts file | **fabricated** |
240
+ | `status: RETRACTED`, `retractedAt <= ts` | **retracted** (before the action) |
241
+ | `status: RETRACTED`, `retractedAt > ts` | **retracted** (_cascade_: in force then, withdrawn later in the session) |
242
+ | `status: SUPERSEDED` | grounded, with a note - the kernel's own rule is that a superseded fact was in force in its version |
243
+ | otherwise | grounded |
244
+
245
+ When an action also carries a `claim`, the fact set is presented to
246
+ `@tcpcore1/truth-kernel`'s `AdversarialVerifier` - reached as a generated copy under
247
+ `src/vendor/truth-kernel/`, for the reason given under [The Merkle roots](#the-merkle-roots) -
248
+ and its verdict is folded in: `CONTRADICTED`
249
+ makes the action **fabricated** and names the fact that decided it, `SILENT` falls back to the
250
+ grounding finding. The comparison rules - normalised text, equal number sets, `CONSTRAINT` and
251
+ `POLICY` checked before support, a `DEFINITION` of non-existence outranking everything,
252
+ retracted facts silent - are the kernel's, not a second copy of them.
253
+
254
+ An action's category is the worst of its findings, in the order
255
+ `fabricated > retracted > ungrounded > grounded` - the same "a conflict's safe reading is the
256
+ refusal" order the API's verdict gate uses for approvals.
257
+
258
+ ## The Merkle roots
259
+
260
+ Two roots, both computed by the truth kernel's `corpusRoot` - the same construction that
261
+ commits a published corpus, with its `0x00`/`0x01` domain bytes, sorted leaves and odd-node
262
+ promotion. **This package contains no Merkle implementation of its own**; it hashes records and
263
+ calls the kernel's helper. A second tree would be a divergence waiting to happen, and this
264
+ repository has been bitten by exactly that.
265
+
266
+ The kernel is not on npm, so "calls the kernel's helper" is delivered as a generated copy:
267
+ `scripts/bundle-kernel.mjs` rewrites `packages/truth-kernel/src/merkle.ts` into
268
+ `src/vendor/truth-kernel/merkle.ts` on every build, and the copy carries a banner naming its
269
+ source and forbidding hand edits. Two tests keep that honest, and they are the reason a copy is
270
+ safe here: `src/__tests__/merkle-parity.test.ts` asserts this package's roots are
271
+ **byte-identical** to the kernel's over empty input, one leaf, two leaves, an odd count,
272
+ unicode content and JSON containing a nested array; and
273
+ `packages/truth-kernel/src/__tests__/merkle-parity.test.ts` makes the same assertion from the
274
+ kernel's side, so a change to the kernel's tree fails the kernel's own suite rather than the
275
+ next build. `src/__tests__/vendor-drift.test.ts` fails if any vendored copy stops being a fresh
276
+ copy of its source.
277
+
278
+ - **`actions`** commits to every field of every action. This is the tamper-evident one:
279
+ change one byte of an argument, a result or a citation and the root changes.
280
+ - **`citations`** commits to the _set_ of fact ids the session cites, deduplicated - the value
281
+ a second party can compare against a fact set they obtained elsewhere.
282
+
283
+ Because `corpusRoot` sorts its leaves, both roots commit to a multiset and not to line order.
284
+ For citations that is exactly right. For actions it means a file with the same actions and
285
+ the same `ts` values in a different line order shares a root - which is correct, because the
286
+ session's semantics are the (action, ts) pairs.
287
+
288
+ To use the root as a tamper check, paste it into a review and re-run with it:
289
+
290
+ ```bash
291
+ tcpcore verify session.jsonl --facts facts.jsonl --expect-root <64 hex>
292
+ ```
293
+
294
+ A mismatch exits `1` and prints both roots.
295
+
296
+ ## Exit codes
297
+
298
+ | Code | Meaning |
299
+ | ---- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
300
+ | `0` | Every action grounded, or ungrounded without `--strict`. |
301
+ | `1` | At least one **fabricated**, at least one **retracted**, or the session does not match `--expect-root`. |
302
+ | `2` | Invalid input: unreadable file, bad JSON, schema violation, unknown flag or adapter, duplicate id, a `RETRACTED` fact with no `retractedAt`, or a session with no actions. |
303
+
304
+ `2` also covers an internal error, deliberately: a crash must never be read as a verdict, and
305
+ a CI job that saw `1` must not have to wonder whether the tool or the agent failed. The message
306
+ says `internal error`, so a human can tell them apart.
307
+
308
+ `retracted` fails the run because a report that reads green while a cited fact was withdrawn
309
+ mid-session is the exact defect this tool exists to find. `--strict` is the only path on which
310
+ `ungrounded` becomes fatal.
311
+
312
+ ## CLI
313
+
314
+ ```
315
+ tcpcore verify <session.jsonl> --facts <facts.jsonl>
316
+ [--json] [--out <path>] [--html <path>] [--merkle]
317
+ [--expect-root <64 hex>] [--strict] [--fact-tool <name>]...
318
+ tcpcore adapt <input|url> --from <adapter> [-o session.jsonl]
319
+ tcpcore adapt --list
320
+ ```
321
+
322
+ - `--json` prints the machine-readable report (schema: `tcpcore.report.v1`).
323
+ - `--out` writes that JSON report to a file; the human summary stays on the terminal.
324
+ - `--html` writes a self-contained page, no JavaScript, no external stylesheet - a report that
325
+ needs a CDN to render is not evidence.
326
+ - `--merkle` also prints the per-action leaves, in the order the tree sorts them.
327
+ - `--fact-tool` names a tool whose results supply facts. **Repeatable.** The verifier reads
328
+ fact ids from `{"facts": [{"id": "..."}]}` in that tool's result, and refuses a result it
329
+ cannot read rather than reporting every later action as ungrounded.
330
+
331
+ ## Adapters
332
+
333
+ `tcpcore adapt` maps a framework's own log into `tcpcore-v1.jsonl`. Every adapter is a real
334
+ mapping module with a committed fixture and a test, and every row below states **where the
335
+ knowledge of that format came from**. This build ships **eleven** adapters: the five of Wave 0
336
+ and the six of W1.3. **Exactly one of the eleven is `captured`** - the DeepSeek Harness one,
337
+ from a real session store on the machine this was built on. The other ten are `documented`
338
+ against a pinned package, type definition or doc page, named in the row and in
339
+ `fixtures/PROVENANCE.md`. None is `inferred`.
340
+
341
+ | Adapter | Format followed | Provenance | What it cannot express |
342
+ | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
343
+ | `langchain` | `@langchain/core@1.3.3` `StreamEvent` (`dist/tracers/event_stream.d.ts:42-85`), `astreamEvents` v2 | **documented** (pinned type definition) | A `StreamEvent` has **no timestamp**, so each capture line must carry its own `"at"`; a log without them is refused, never dated with the verifier's clock. |
344
+ | `langgraph` | `@langchain/langgraph@1.6.0` `streamEvents` v2 (`dist/pregel/types.d.ts:19`, `dist/pregel/index.d.ts:493-498`), `metadata.langgraph_node` | **documented** (pinned type definition) | Only `streamEvents` v2 is read. `updates`-mode chunks carry no timestamp and no run id, and the v3 protocol's shape was not verifiable here, so both are refused by name. |
345
+ | `claude-code` | Claude Code session transcript JSONL (hooks' `transcript_path`; third-party field reference) | **documented** (third-party; not re-verified) | Anthropic does not publish the transcript schema and there was no Claude Code install to capture from. A `tool_use` with no `tool_result` is refused rather than reported as grounded or failed. |
346
+ | `deepseek-harness` | DeepSeek Harness v4 session log, verified field by field from a real capture | **captured** (redacted fixture) | The on-disk log is multi-frame zstd and Node decompresses only the first frame, so the input must be `zstd -d`'d first; a compressed file is refused rather than partially read. |
347
+ | `cursor` | Cursor agent hooks (`cursor.com/docs/hooks`): generic `preToolUse` / `postToolUse` / `postToolUseFailure` payloads with `tool_name`, `tool_input`, `tool_output`, `isError` | **documented** (`cursor.com/docs/hooks`) | Hooks carry no call id, so pairing is by conversation, tool name and order; an unmatched payload **or two outstanding calls to one tool in one conversation** is refused rather than paired by guess. MCP output redaction is described below. |
348
+ | `openai-assistants` | Assistants API run steps (`openai@7.47.0`, `resources/beta/threads/runs/steps.d.ts`): `FunctionToolCall.function.{name,arguments,output}`, `created_at` in seconds | **documented** (pinned type definition) | Only function calls. `file_search` carries no arguments and `code_interpreter` carries code, so both are refused; a `null` output means the outputs were never submitted and is refused too. |
349
+ | `aws-bedrock-agents` | Trace parts (`@aws-sdk/client-bedrock-agent-runtime@3.1161.0`, `dist-types/models/models_0.d.ts:3595`): `orchestrationTrace.invocationInput` / `.observation` | **documented** (pinned type definition) | Trace parts carry no call id, so pairing is positional **and interleaved invocations are refused** rather than mispaired: a trace it accepts is a trace it could pair. Parameters are name-to-string, so a declared grounding cannot be expressed at all. |
350
+ | `google-vertex-agents` | ADK session events (`google-adk@2.13.0`, `google/adk/events/event.py`): `content.parts[].function_call` / `function_response` | **documented** (pinned wheel source) | A call is paired by invocation and function name; two outstanding calls to one function are refused as ambiguous, and a call with no response (a long-running tool) is refused. |
351
+ | `microsoft-autogen` | `autogen_core` structured logging (`autogen-core@0.7.5`, `autogen_core/logging.py:160-199`): `{"type":"ToolCall","tool_name","arguments","result","agent_id"}` | **documented** (pinned wheel source) | The event payload carries **no timestamp**, so every line must carry `ts`/`timestamp`/`asctime`; a line without one is refused rather than dated with the verifier clock. |
352
+ | `crewai` | Event bus (`crewai 1.15.34`): `ToolUsageStartedEvent` / `ToolUsageFinishedEvent` / `ToolUsageErrorEvent` from `docs.crewai.com` and `lib/crewai/src/crewai/events/` | **documented** (docs + GitHub source, fetched this wave) | Only the event bus is read - see below. A started tool with no finished event is refused, and a tool-reported `failure` has no representation in the canonical format. |
353
+ | `custom-http` | Canonical JSONL over a file or an HTTP GET (`spec/tcpcore-v1.md`) | **documented** (specified by us) | It reads canonical records and nothing else: a producer must emit `tcpcore.session.v1` actions itself. A JSON array body is refused, because line framing is part of the format. |
354
+
355
+ Provenance, in one line each: **captured** means a real log from that framework was used;
356
+ **documented** means the format was read from a pinned package, type definition or doc page,
357
+ named in the row; **inferred** means neither was available and the README would say so. No
358
+ adapter in this build is `inferred`.
359
+
360
+ ### CrewAI: the event bus, not `TaskOutput`
361
+
362
+ CrewAI also exposes `CrewOutput` / `TaskOutput.tools_used[]`, and it is the smaller surface to
363
+ read. It is not used here, because each entry is `{ tool, input, output }` and **`input` is a
364
+ string** - the model's raw tool-call text. A string is not a structured argument set: mapping it
365
+ would mean parsing prose to invent `args`, which is exactly the coercion this format refuses.
366
+ The event bus carries `tool_args` as a dict and the tool's own `output`, so it is the surface
367
+ that already speaks in structured claims. `tool_usage_finished` is the unit mapped (it carries
368
+ `tool_name`, `tool_args`, `started_at` and `output`), and `started_at` is the action's
369
+ timestamp.
370
+
371
+ ### custom-http: the escape hatch
372
+
373
+ The twelfth framework should not need a twelfth module. `tcpcore adapt <url> --from custom-http`
374
+ performs one HTTP GET (ten-second timeout, 5 MB body cap, non-2xx is invalid input) and reads
375
+ canonical JSONL; a file path works the same way. It is a reader, not a service, and it validates
376
+ through the same parser as a hand-written session, so an unknown key fails with the same message
377
+ it would anywhere else.
378
+
379
+ ### Cursor and redacted MCP output
380
+
381
+ Cursor's `postToolUse` hook **redacts MCP tool output** for HTTP MCP servers - it receives
382
+ `{"redacted": true}` (with `isError` when known) instead of the result. Sessions that rely on
383
+ MCP therefore report more `ungrounded` actions than grounded. This is a limitation of Cursor's
384
+ logging, not of the verifier: when the log redacts the result, the action is marked
385
+ `ungrounded` rather than `fabricated`, so the report stays honest about what it cannot
386
+ adjudicate.
387
+
388
+ The reserved result shape `{"redacted": true}` is how that reaches the report: the adapter
389
+ passes the hook's payload through unchanged, and the engine treats an action that would
390
+ otherwise be grounded as ungrounded. A fabricated or retracted citation still wins, because
391
+ those findings come from the grounding, which the log did give us.
392
+
393
+ ### The third-party-hooks question
394
+
395
+ Cursor's "Third Party Hooks" page documents that Cursor **loads Claude Code hook
396
+ configurations** and maps hook names and response formats. It says nothing about the _input
397
+ payload_ matching Claude Code's. So the conclusion is **configuration compatibility, not
398
+ payload compatibility**: `claude-code.ts` reads a transcript, `cursor.ts` reads hook payloads,
399
+ and they are two independent parsers that share the tool-call mapping in `adapters/tool-call.ts`
400
+ rather than an envelope.
401
+
402
+ ## What this does not prove
403
+
404
+ - **It does not prove an adapter is right.** Eleven adapters, **one `captured`**, **ten
405
+ `documented`**, and **no adapter silently guesses**: where a framework's log does not carry
406
+ what the format needs, the adapter refuses with a message naming the field rather than
407
+ inventing a value, and where a pairing would be ambiguous - two Bedrock invocations before an
408
+ observation, two Cursor calls to one tool in one conversation - it refuses the trace instead
409
+ of pairing it by guess. **A `documented` adapter is a claim about a document, not about your
410
+ logs**: it was written against a pinned package, type definition or doc page, and if the real
411
+ format differs the adapter fails on the field rather than mapping around it.
412
+ - **It does not prove the facts are true.** A fact is a typed assertion with a source; a
413
+ source can be wrong. Verify against a kernel full of `VERIFIED` facts and the report is a
414
+ statement about well-sourced beliefs, not about reality.
415
+ - **It does not judge whether a citation is apt.** An agent can cite a real, live, irrelevant
416
+ fact and be reported `grounded`. Detecting that needs semantic entailment, which is unsolved;
417
+ self-declared citations are marked `declared: true` so a reader can see which claims rest on
418
+ the agent's own account of its evidence.
419
+ - **It does not extract claims from prose.** Claims and grounding come only from structured
420
+ fields. An adapter that guessed would manufacture the findings the tool exists to detect.
421
+ - **It does not replay publication history.** The fact set is treated as one corpus in force.
422
+ "Which version was current at time T" is the kernel's question, and it has its own model for
423
+ it.
424
+ - **It does not detect a deleted action.** The roots commit to the actions that are in the
425
+ file. A session with a line removed is a different session with a different root - which is
426
+ what `--expect-root` is for - but nothing in a single log can prove that nothing was removed
427
+ before the root was computed.
428
+ - **It does not replace reading the findings.** Four numbers are a summary. The value is in the
429
+ lines that name the action and the fact.
430
+ - **Plausible-zero detection is not in this build.** The design's W1.4 checks ("claimed zero
431
+ issues but checked no files", "claimed success but returned an empty result", "claimed to
432
+ have run tests but never invoked a test tool", "claimed to have read a file but never called
433
+ read") need semantics this wave does not have. The report carries a `plausibleZero` array,
434
+ empty, and the registry it will be filled from; a session with no actions is refused
435
+ outright, which is the plausible-zero rule applied to the session itself.
436
+
437
+ ## Development
438
+
439
+ ```bash
440
+ pnpm --filter @tcpcore1/verifier build # bundle the kernel, then tsc -> dist
441
+ pnpm --filter @tcpcore1/verifier typecheck
442
+ pnpm --filter @tcpcore1/verifier test # vitest run --silent
443
+ pnpm --filter @tcpcore1/verifier check:vendor # is src/vendor a fresh copy of the kernel?
444
+ pnpm --filter @tcpcore1/verifier check:tarball # pack, extract, refuse a tarball that cannot install
445
+ npx eslint packages/verifier
446
+ ```
447
+
448
+ `build` runs `node scripts/bundle-kernel.mjs` before `tsc`, so `src/vendor/` is rewritten from
449
+ `packages/truth-kernel` and `packages/claim-graph` on every build. Never edit `src/vendor/`: the
450
+ copy is generated, it says so in its header, and `src/__tests__/vendor-drift.test.ts` fails if
451
+ it stops being a fresh copy. `prepublishOnly` runs both checks, so a stale copy or a tarball
452
+ carrying a `workspace:`/`file:` dependency cannot be published.
453
+
454
+ Adding this package to a checkout needs one `pnpm install`: `@tcpcore1/truth-kernel` is a
455
+ **devDependency** (tests and typecheck import the real kernel; consumers never install it), and
456
+ the workspace link plus the `pnpm-lock.yaml` importer entry have to exist. The lockfile change is
457
+ the operator's - and moving a dependency between `dependencies` and `devDependencies` in
458
+ `package.json` requires regenerating that importer, because CI installs with
459
+ `--frozen-lockfile`.
460
+
461
+ ## Licence
462
+
463
+ MIT. See [LICENSE](./LICENSE).
464
+
465
+ Every committed fixture states where its shape came from; see
466
+ [`fixtures/PROVENANCE.md`](./fixtures/PROVENANCE.md).
package/bin/tcpcore.js ADDED
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `tcpcore` bin shim.
4
+ *
5
+ * Deliberately tiny: it resolves the built entry point and, if the package has not
6
+ * been built, says which command builds it. That failure is the one a developer
7
+ * hits after `npm i` from a git checkout, and "Cannot find module" is a worse
8
+ * message than the two lines below.
9
+ *
10
+ * Nothing else lives here. Every decision - argv, exit codes, output - belongs in
11
+ * `src/`, where it is importable and testable. A bin that contains logic is a bin
12
+ * whose logic cannot be unit-tested.
13
+ */
14
+ import { existsSync } from 'node:fs';
15
+ import { dirname, join } from 'node:path';
16
+ import { fileURLToPath, pathToFileURL } from 'node:url';
17
+
18
+ const here = dirname(fileURLToPath(import.meta.url));
19
+ const entry = join(here, '..', 'dist', 'index.js');
20
+
21
+ if (!existsSync(entry)) {
22
+ process.stderr.write(
23
+ [
24
+ 'tcpcore is not built yet.',
25
+ '',
26
+ ' pnpm --filter @tcpcore1/verifier build',
27
+ '',
28
+ `Expected: ${entry}`,
29
+ '',
30
+ ].join('\n'),
31
+ );
32
+ process.exit(2);
33
+ }
34
+
35
+ await import(pathToFileURL(entry).href);
@@ -0,0 +1,46 @@
1
+ /**
2
+ * AWS Bedrock Agents adapter: the `trace` parts of an `InvokeAgent` response, or
3
+ * `GetAgentTrace` output.
4
+ *
5
+ * ## The format, and where it was read
6
+ *
7
+ * `@aws-sdk/client-bedrock-agent-runtime@3.1161.0`,
8
+ * `dist-types/models/models_0.d.ts`:
9
+ *
10
+ * - `TracePart { sessionId?, trace?: Trace, callerChain?, eventTime?: Date,
11
+ * collaboratorName?, agentId?, agentAliasId?, agentVersion? }` (line 3595)
12
+ * - `Trace` is a tagged union (line 3460); the interesting arm is
13
+ * `trace.orchestrationTrace`, itself a union carrying `invocationInput`,
14
+ * `observation` or `rationale`.
15
+ * - `orchestrationTrace.invocationInput.actionGroupInvocationInput`:
16
+ * `{ actionGroupName, verb, apiPath, parameters?: Parameter[], requestBody?, … }`
17
+ * - `orchestrationTrace.observation.actionGroupInvocationOutput: { text?, metadata? }`
18
+ * - `Parameter { name?, type?, value? }`
19
+ *
20
+ * ## Pairing: positional, and refusing to guess
21
+ *
22
+ * **The trace parts carry no call id**, so an invocation is paired with the next
23
+ * `actionGroupInvocationOutput` in file order. Every case where that pairing would be a
24
+ * guess is a hard error:
25
+ *
26
+ * - an invocation with no following observation (the outcome is unknown);
27
+ * - an observation with no preceding invocation (the call is missing);
28
+ * - **a second invocation before the first observation** - interleaved calls cannot be
29
+ * paired positionally, and a guessed pairing would put one call's output against
30
+ * another call's claim *silently*, which is the one failure this package exists to
31
+ * find in other people's output and must not produce itself.
32
+ *
33
+ * So the contract is narrow and true: **a trace this adapter accepts is a trace it could
34
+ * pair.** It refuses to verify a trace it would have to guess about.
35
+ *
36
+ * ## Events that are not actions
37
+ *
38
+ * `rationale` (the agent's reasoning), `modelInvocationInput`/`Output` (prompt
39
+ * plumbing), `preProcessingTrace`, `postProcessingTrace`, `guardrailTrace` and
40
+ * `failureTrace` are not mapped. They are not claims about what was done, and turning
41
+ * a rationale into an action would invent one.
42
+ */
43
+ import { type Adapter } from './types.js';
44
+ /** The Bedrock Agents adapter. */
45
+ export declare const awsBedrockAgentsAdapter: Adapter;
46
+ //# sourceMappingURL=aws-bedrock-agents.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aws-bedrock-agents.d.ts","sourceRoot":"","sources":["../../src/adapters/aws-bedrock-agents.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAIH,OAAO,EAAyB,KAAK,OAAO,EAAE,MAAM,YAAY,CAAC;AA4BjE,kCAAkC;AAClC,eAAO,MAAM,uBAAuB,EAAE,OA4IrC,CAAC"}