@tormentalabs/claude-code-wire-compat 0.1.0-rc.16 → 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.
package/CHANGELOG.md CHANGED
@@ -2,7 +2,138 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
- ## [0.1.0-rc.16] - Unreleased
5
+ ## [0.1.0] - 2026-07-28
6
+
7
+ First stable release.
8
+
9
+ There is NO runtime code change relative to `0.1.0-rc.17`:
10
+ `git diff --stat 5c6881b d7e2901 -- src package.json` is empty, so `dist/` is
11
+ byte-identical to the `0.1.0-rc.17` build. Everything this release contains is
12
+ described in the release candidate entries below; they are not repeated here.
13
+ The packed content differs from `0.1.0-rc.17` by `README.md` and by the added
14
+ TypeScript sources described below — the executed code is unchanged, which
15
+ `npm run test:pack` confirms by an unchanged consumer digest.
16
+
17
+ ### Added
18
+
19
+ - **The tarball now ships the TypeScript sources.** `src` is added to the
20
+ `files` allowlist. The build emits 40 `.js.map` and `.d.ts.map` files that
21
+ reference `../src/*.ts` and carry no `sourcesContent`, so every source map in
22
+ the published package resolved to nothing in a consumer's debugger. Shipping
23
+ the sources is also the coherent option for a GPL-3.0-or-later package whose
24
+ `NOTICE` already offers corresponding source: the offer and the tarball now
25
+ agree. Only `.ts` files are packed under `src/`, pinned exactly by
26
+ `test/pack/pack-policy.test.ts`; the allowlist itself is pinned by
27
+ `test/governance/release-policy.test.ts` and
28
+ `test/governance/package-policy.test.ts`.
29
+ - The ported protocol-documentation corpus and `docs/ATTRIBUTION.md` are now in
30
+ the repository. Neither is packed into the tarball.
31
+
32
+ ### Changed
33
+
34
+ - `README.md` no longer describes the package as a bootstrap release candidate
35
+ whose request-building behavior arrives later. That statement was false — the
36
+ package builds and parses requests — and it was the text rendered on the
37
+ npmjs.com package page.
38
+
39
+ ## [0.1.0-rc.17] - 2026-07-28
40
+
41
+ ### Added
42
+
43
+ - **Root `suppressIdentityBlock` (seam S8).** The canonical identity block was
44
+ composed unconditionally, so a consumer switch whose meaning is "send neither
45
+ canonical block" — `token_economy.lean_system_non_main` in the `opencode`
46
+ plugin, which removed billing AND identity before it migrated to this package
47
+ — was a silent no-op. `suppressIdentityBlock: true` omits the identity block
48
+ entirely, which with `suppressBillingBlock` makes four canonical prefixes
49
+ legitimate: `[billing, identity]`, `[identity]`, `[billing]` and `[]`. Only a
50
+ boolean is accepted; anything else is `INVALID_INPUT`.
51
+ `evidence.identityBlockSuppressed` is emitted only when the block was actually
52
+ removed, mirroring `billingBlockSuppressed` exactly, so evidence stays
53
+ byte-identical for every request that ignores the seam. Recorded in
54
+ `docs/source-trace.md` as governance ledger L16.
55
+
56
+ This is a DIFFERENT field from the L10 `cacheControl.suppressIdentityBlock`,
57
+ which keeps the block and drops only its `cache_control` marker. The name
58
+ collision is deliberate — symmetry with `suppressBillingBlock` at the root was
59
+ judged worth more than a novel name — and the JSDoc of each field states what
60
+ it does and names the other by its full path.
61
+
62
+ The caller-block drop stays UNCONDITIONAL: a caller block byte-equal to the
63
+ identity text is removed even when the canonical one was suppressed, matching
64
+ the genuine client and keeping the parser's absence check below sound.
65
+
66
+ - **`preserveThinkingBlockCacheControl` (seam S9).** `thinking` and
67
+ `redacted_thinking` blocks were pinned to a strict allowlist — `signature`,
68
+ `thinking`, `type` and `data`, `type` — so a request carrying `cache_control`
69
+ on a reasoning block was rejected outright with `INVALID_INPUT`. The consumer
70
+ had no legal way out, and this is a PRODUCTION failure rather than a test
71
+ artefact: the Anthropic API answers a mutated reasoning block with
72
+
73
+ > `400 ... thinking or redacted_thinking blocks in the latest assistant`
74
+ > `message cannot be modified. These blocks must remain as they were in the`
75
+ > `original response.`
76
+
77
+ so `delete block.cache_control` before the call is itself the modification
78
+ that triggers the 400. `preserveThinkingBlockCacheControl: true` accepts the
79
+ key and copies it to the body VERBATIM — caller key order intact, no TTL
80
+ applied, no breakpoint placed. Only a boolean is accepted; anything else is
81
+ `INVALID_INPUT`.
82
+
83
+ The allowlist grows by `cache_control` and by NOTHING else: an unknown key on
84
+ a reasoning block is still `INVALID_INPUT` with the seam active. The value
85
+ passes the same `cache_control` validator every other block uses —
86
+ `{ type: "ephemeral" }` with an optional `ttl` — so a malformed marker still
87
+ fails closed; the `scope` key that `text` blocks tolerate for legacy reasons
88
+ is deliberately NOT accepted, the API never returning it on a reasoning block.
89
+ The marker takes no part in this package's cache-control machinery:
90
+ `applySystemCacheControl` is untouched and `applyMessageCacheControl` already
91
+ exempts reasoning blocks from both the strip and the breakpoint pass.
92
+
93
+ `evidence.thinkingBlockCacheControlPreserved` is emitted only when the seam
94
+ was active AND at least one emitted block actually carried a marker — opting
95
+ in without using it records nothing — mirroring `billingBlockSuppressed`
96
+ exactly, so evidence stays byte-identical for every request that ignores the
97
+ seam. `parseBuiltClaudeCodeRequest` CONFIRMS that claim against the body
98
+ before it checks byte length or digest, so a forgery that is byte-length
99
+ preserving and evidence-self-consistent is refused by the structural check
100
+ rather than incidentally by arithmetic. Recorded in `docs/source-trace.md` as
101
+ governance ledger L17.
102
+
103
+ ### Changed
104
+
105
+ - **`parseBuiltClaudeCodeRequest` no longer INFERS the canonical prefix from the
106
+ identity block's position; it READS the length from evidence and VERIFIES it
107
+ structurally.** With two independent seams an empty prefix is
108
+ indistinguishable from a caller-only array, so position inference is no longer
109
+ decidable: the rc.16 discriminator would have hit its unconditional failure
110
+ path on every request built with both seams active. The parser now takes
111
+ `evidence.billingBlockSuppressed` and `evidence.identityBlockSuppressed` as
112
+ the claimed prefix length and confirms every block that claim implies —
113
+ billing by its fixed `x-anthropic-billing-header: cc_version=` head (the tail
114
+ is per-request), identity by the byte-exact identity text. This is strictly
115
+ stronger than what it replaces, which never inspected the billing slot at all:
116
+ an envelope built with `suppressBillingBlock` whose evidence hid that fact was
117
+ previously accepted.
118
+
119
+ Verification is **asymmetric, deliberately**. A claim that identity was
120
+ suppressed is refuted by finding the identity text ANYWHERE in the array,
121
+ which is sound because `buildCanonicalSystem` drops caller blocks equal to it
122
+ unconditionally and merges runs with `\n`, so the text cannot legitimately
123
+ survive. There is no mirror check for billing: a caller block may legitimately
124
+ begin with the billing header text, so its presence proves nothing.
125
+
126
+ The match is on TEXT, never on `cache_control` — the L10 seam can legitimately
127
+ emit the identity block with no marker — and the assertion remains an
128
+ EQUALITY.
129
+
130
+ - **`emittedSystemBlockCount` is computed from both seams instead of a
131
+ constant.** `CANONICAL_SYSTEM_BLOCKS` and `CANONICAL_SYSTEM_BLOCKS_WITHOUT_BILLING`
132
+ were removed: a constant cannot express the empty prefix the two seams produce
133
+ together. The build path now subtracts one slot per canonical block that
134
+ actually survived.
135
+
136
+ ## [0.1.0-rc.16] - 2026-07-28
6
137
 
7
138
  ### Added
8
139
 
package/README.md CHANGED
@@ -4,7 +4,7 @@ Claude Wire Compat
4
4
 
5
5
  An ESM-only, runtime-neutral TypeScript package for constructing a pinned Claude Code Messages wire contract.
6
6
 
7
- > **Status:** bootstrap release candidate. Runtime request-building behavior is intentionally introduced in later releases.
7
+ > **Status:** stable. `buildClaudeCodeRequest` and `parseBuiltClaudeCodeRequest` build and read back the pinned wire contract. The package performs no I/O: credentials, transport, persistence, refresh coordination, and retries remain the consumer's responsibility.
8
8
 
9
9
  ## Package
10
10
 
@@ -18,6 +18,58 @@ The package targets Node.js 20 or newer and is designed to remain portable to Bu
18
18
 
19
19
  The only accepted `profile` value is the exported `CLAUDE_CODE_2_1_195_PROFILE` singleton. Any other object, even a structurally identical clone, is rejected with `ClaudeCodeWireError` code `INVALID_INPUT`. This deliberate fail-closed behaviour prevents callers from substituting an unpinned protocol profile.
20
20
 
21
+ ## Protocol documentation
22
+
23
+ The wire contract this package pins was reverse engineered before it was
24
+ implemented. That research is preserved under [`docs/protocol/`](./docs/protocol)
25
+ so a future maintainer can re-derive the contract when Claude Code ships a new
26
+ version. It is ported verbatim from
27
+ [opencode-anthropic-fix](https://github.com/marco-jardim/opencode-anthropic-fix)
28
+ at commit `466d500` under GPL-3.0-or-later; every file carries a provenance
29
+ header and is listed in [docs/ATTRIBUTION.md](./docs/ATTRIBUTION.md).
30
+
31
+ **These documents describe the upstream plugin and Claude Code itself, not this
32
+ package's API.** The normative mapping from those findings to this package's
33
+ contract is [docs/source-trace.md](./docs/source-trace.md).
34
+
35
+ Protocol knowledge corpus:
36
+
37
+ - [Reverse engineering](./docs/protocol/reverse-engineering.md) — the full
38
+ authentication and API reverse-engineering record.
39
+ - [HTTP headers and system prompt](./docs/protocol/http-headers-and-system-prompt.md)
40
+ — header composition and system-prompt mimicry.
41
+ - [Fingerprint extraction](./docs/protocol/fingerprint-extraction.md) — how the
42
+ client fingerprint and metadata are derived.
43
+ - [Message flow](./docs/protocol/message-flow.md) — end-to-end request and
44
+ response flow.
45
+ - [Tool use examples](./docs/protocol/tool-use-examples.md) — tool_use and
46
+ tool_result pairing in practice.
47
+ - [Code comparison reference](./docs/protocol/code-comparison-reference.md) —
48
+ side-by-side comparison against the genuine client.
49
+ - [Divergence analysis](./docs/protocol/divergence-analysis.md) — every observed
50
+ divergence from the genuine client.
51
+ - [Divergence executive summary](./docs/protocol/divergence-executive-summary.md)
52
+ — the condensed version of that analysis.
53
+ - [Quick reference](./docs/protocol/quick-reference.md) — condensed lookup of
54
+ headers, betas, and switches.
55
+ - [System-prompt search results](./docs/protocol/system-prompt-search-results.md)
56
+ — where each system-prompt fragment was found in the analysed build.
57
+ - [Cache transparency](./docs/protocol/cache-transparency.md) — anti-verbosity
58
+ and prompt-cache observability.
59
+ - [Beta decision table](./docs/protocol/beta-decision-table.md) — which beta
60
+ identifiers are sent under which conditions.
61
+
62
+ Per-version wire analyses — [why they exist and when a new one is
63
+ required](./docs/protocol/versions/README.md):
64
+
65
+ - [Claude Code 2.1.119](./docs/protocol/versions/claude-code-2.1.119-analysis.md)
66
+ - [Claude Code 2.1.133](./docs/protocol/versions/claude-code-2.1.133-analysis.md)
67
+ - [Claude Code 2.1.143](./docs/protocol/versions/claude-code-2.1.143-analysis.md)
68
+ - [Claude Code 2.1.150](./docs/protocol/versions/claude-code-2.1.150-analysis.md)
69
+ - [Claude Code 2.1.159](./docs/protocol/versions/claude-code-2.1.159-analysis.md)
70
+ - [Claude Code 2.1.195](./docs/protocol/versions/claude-code-2.1.195-analysis.md)
71
+ — the release this package's profile pins.
72
+
21
73
  ## Development
22
74
 
23
75
  ```sh
@@ -1 +1 @@
1
- {"version":3,"file":"build-request.d.ts","sourceRoot":"","sources":["../src/build-request.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,iCAAiC,EACjC,sBAAsB,EAMtB,yBAAyB,EACzB,0BAA0B,EAC1B,sBAAsB,EAGvB,MAAM,gBAAgB,CAAC;AA+mCxB,2DAA2D;AAC3D,wBAAsB,iCAAiC,CACrD,KAAK,EAAE,0BAA0B,EACjC,OAAO,GAAE,yBAAuD,GAC/D,OAAO,CAAC,iCAAiC,CAAC,CAiF5C;AAED;;;;;;;;GAQG;AACH,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,sBAAsB,EAC7B,OAAO,GAAE,yBAAuD,GAC/D,OAAO,CAAC,sBAAsB,CAAC,CA6IjC;AAED;;;;;;;;GAQG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,OAAO,EACd,OAAO,GAAE,yBAAuD,GAC/D,sBAAsB,CAgFxB"}
1
+ {"version":3,"file":"build-request.d.ts","sourceRoot":"","sources":["../src/build-request.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,iCAAiC,EACjC,sBAAsB,EAMtB,yBAAyB,EACzB,0BAA0B,EAC1B,sBAAsB,EAGvB,MAAM,gBAAgB,CAAC;AAwvCxB,2DAA2D;AAC3D,wBAAsB,iCAAiC,CACrD,KAAK,EAAE,0BAA0B,EACjC,OAAO,GAAE,yBAAuD,GAC/D,OAAO,CAAC,iCAAiC,CAAC,CAiF5C;AAED;;;;;;;;GAQG;AACH,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,sBAAsB,EAC7B,OAAO,GAAE,yBAAuD,GAC/D,OAAO,CAAC,sBAAsB,CAAC,CA8JjC;AAED;;;;;;;;GAQG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,OAAO,EACd,OAAO,GAAE,yBAAuD,GAC/D,sBAAsB,CA+FxB"}
@@ -54,6 +54,8 @@ const INPUT_KEYS = new Set([
54
54
  "additionalBetas",
55
55
  "suppressBetas",
56
56
  "suppressBillingBlock",
57
+ "suppressIdentityBlock",
58
+ "preserveThinkingBlockCacheControl",
57
59
  "betaOverrides",
58
60
  "metadataOverrides",
59
61
  "extraHeaders",
@@ -81,10 +83,16 @@ const COUNT_TOKENS_INPUT_KEYS = new Set([
81
83
  "extraHeaders",
82
84
  ]);
83
85
  const BUILT_KEYS = new Set(["url", "method", "headers", "body", "evidence"]);
84
- /** Counts the billing and identity blocks of the default canonical prefix. */
85
- const CANONICAL_SYSTEM_BLOCKS = 2;
86
- /** Counts the identity block alone, once `suppressBillingBlock` removed billing. */
87
- const CANONICAL_SYSTEM_BLOCKS_WITHOUT_BILLING = 1;
86
+ /**
87
+ * The self-describing prefix of the canonical billing block's text.
88
+ *
89
+ * `buildBillingBlock` emits
90
+ * `x-anthropic-billing-header: cc_version=<version>.<fingerprint>; ...`, whose
91
+ * tail varies per request, so only this fixed head can anchor a structural
92
+ * check. It is what lets the parser CONFIRM that a request which claims to
93
+ * carry the billing block actually carries it.
94
+ */
95
+ const BILLING_BLOCK_TEXT_PREFIX = "x-anthropic-billing-header: cc_version=";
88
96
  const EVIDENCE_KEYS = new Set([
89
97
  "profileId",
90
98
  "url",
@@ -100,6 +108,8 @@ const EVIDENCE_KEYS = new Set([
100
108
  "droppedExtraHeaderNames",
101
109
  "suppressedBetaNames",
102
110
  "billingBlockSuppressed",
111
+ "identityBlockSuppressed",
112
+ "thinkingBlockCacheControlPreserved",
103
113
  ]);
104
114
  const CAPABILITY_KEYS = [
105
115
  "thinking",
@@ -511,6 +521,26 @@ function validateExtraHeaderPolicy(value) {
511
521
  function validateSuppressBillingBlock(value) {
512
522
  return parseBoolean(value);
513
523
  }
524
+ /**
525
+ * Validates the package-extension identity-block suppression flag.
526
+ *
527
+ * Same contract as `validateSuppressBillingBlock`: only a boolean states a
528
+ * decision, because coercing a truthy string would silently drop a canonical
529
+ * block the genuine client always sends.
530
+ */
531
+ function validateSuppressIdentityBlock(value) {
532
+ return parseBoolean(value);
533
+ }
534
+ /**
535
+ * Validates the package-extension thinking-block `cache_control` seam flag.
536
+ *
537
+ * Same contract as the suppression flags: only a boolean states a decision,
538
+ * because coercing a truthy string would silently widen the thinking-block
539
+ * allowlist this package pins against the genuine client.
540
+ */
541
+ function validatePreserveThinkingBlockCacheControl(value) {
542
+ return parseBoolean(value);
543
+ }
514
544
  function createEffectiveProfile(pinnedProfile, override) {
515
545
  if (override === undefined)
516
546
  return pinnedProfile;
@@ -573,6 +603,12 @@ function validateInput(input) {
573
603
  const suppressBillingBlock = Object.hasOwn(input, "suppressBillingBlock")
574
604
  ? validateSuppressBillingBlock(ownValue(input, "suppressBillingBlock"))
575
605
  : false;
606
+ const suppressIdentityBlock = Object.hasOwn(input, "suppressIdentityBlock")
607
+ ? validateSuppressIdentityBlock(ownValue(input, "suppressIdentityBlock"))
608
+ : false;
609
+ const preserveThinkingBlockCacheControl = Object.hasOwn(input, "preserveThinkingBlockCacheControl")
610
+ ? validatePreserveThinkingBlockCacheControl(ownValue(input, "preserveThinkingBlockCacheControl"))
611
+ : false;
576
612
  return {
577
613
  source: input,
578
614
  clientRequestId,
@@ -581,6 +617,8 @@ function validateInput(input) {
581
617
  betaOverrides,
582
618
  extraHeaderPolicy,
583
619
  suppressBillingBlock,
620
+ suppressIdentityBlock,
621
+ preserveThinkingBlockCacheControl,
584
622
  };
585
623
  }
586
624
  function validateCountTokensInput(input) {
@@ -818,33 +856,102 @@ function parseEvidence(value) {
818
856
  billingBlockSuppressed: parseBoolean(ownValue(value, "billingBlockSuppressed")),
819
857
  }
820
858
  : {}),
859
+ ...(Object.hasOwn(value, "identityBlockSuppressed")
860
+ ? {
861
+ identityBlockSuppressed: parseBoolean(ownValue(value, "identityBlockSuppressed")),
862
+ }
863
+ : {}),
864
+ // Rehydrated here because `assertExactKeys` above already accepts the key:
865
+ // omitting this branch would silently DROP it, and the round-trip equality
866
+ // every seam test asserts would fail on an envelope that is entirely legal.
867
+ ...(Object.hasOwn(value, "thinkingBlockCacheControlPreserved")
868
+ ? {
869
+ thinkingBlockCacheControlPreserved: parseBoolean(ownValue(value, "thinkingBlockCacheControlPreserved")),
870
+ }
871
+ : {}),
821
872
  };
822
873
  }
874
+ /** Reads an own property of a value that is not asserted to be a record. */
875
+ function ownProperty(value, key) {
876
+ return isRecord(value) && Object.hasOwn(value, key)
877
+ ? ownValue(value, key)
878
+ : undefined;
879
+ }
880
+ /**
881
+ * Reports whether the emitted `messages` carry a reasoning block that actually
882
+ * kept a `cache_control` key.
883
+ *
884
+ * This is what makes `evidence.thinkingBlockCacheControlPreserved` a record of
885
+ * what the seam DID rather than of what it was allowed to do, and it is the
886
+ * check that refutes an envelope claiming the seam over a body carrying no such
887
+ * block. Presence of the KEY is the test, not truthiness: `cache_control: null`
888
+ * is a preserved marker too, exactly as it is on every other block type.
889
+ */
890
+ function hasThinkingBlockCacheControl(messages) {
891
+ if (!Array.isArray(messages))
892
+ return false;
893
+ return messages.some((message) => {
894
+ const content = ownProperty(message, "content");
895
+ return (Array.isArray(content) &&
896
+ content.some((block) => {
897
+ const type = ownProperty(block, "type");
898
+ return ((type === "thinking" || type === "redacted_thinking") &&
899
+ ownProperty(block, "cache_control") !== undefined);
900
+ }));
901
+ });
902
+ }
823
903
  /** Reads a system block's `text` without asserting the block's shape. */
824
904
  function systemBlockText(block) {
825
- return isRecord(block) && Object.hasOwn(block, "text")
826
- ? ownValue(block, "text")
827
- : undefined;
905
+ return ownProperty(block, "text");
906
+ }
907
+ /** Recognises the canonical billing block by its fixed, self-describing head. */
908
+ function isBillingBlockText(text) {
909
+ return typeof text === "string" && text.startsWith(BILLING_BLOCK_TEXT_PREFIX);
828
910
  }
829
911
  /**
830
- * Infers how many canonical blocks the emitted `system` array carries.
912
+ * VERIFIES how many canonical blocks the emitted `system` array carries.
913
+ *
914
+ * The root seams `suppressBillingBlock` and `suppressIdentityBlock` make four
915
+ * prefixes legitimate — `[billing, identity]`, `[identity]`, `[billing]` and
916
+ * `[]` — so no probe over the array alone can tell them apart: an empty prefix
917
+ * is indistinguishable from a caller-only array. The prefix length is therefore
918
+ * READ from the evidence flags, which the builder emits only when suppression
919
+ * actually removed a block.
831
920
  *
832
- * The prefix used to be the constant `CANONICAL_SYSTEM_BLOCKS`, but
833
- * `suppressBillingBlock` makes it either `[billing, identity]` or `[identity]`.
834
- * The discriminator is therefore the POSITION of the pinned identity text, not
835
- * arithmetic over the array length and never `cache_control`: the
836
- * `suppressIdentityBlock` seam can emit the identity block with no cache
837
- * marker, so a marker probe would misread a legitimate request. An array whose
838
- * identity block sits at neither position was not produced by this package.
921
+ * Evidence is not trusted blindly. Each block the flags claim is present is
922
+ * confirmed in place: billing by its `x-anthropic-billing-header: cc_version=`
923
+ * head (its tail is per-request), identity by the byte-exact `IDENTITY_TEXT`;
924
+ * a claim that identity was suppressed is confirmed by that text being absent
925
+ * from the whole array. This is strictly stronger than the position probe it
926
+ * replaces, which never checked the billing slot at all. Never `cache_control`:
927
+ * the
928
+ * `cacheControl.suppressIdentityBlock` seam can emit the identity block with no
929
+ * marker, so a marker probe would misread a legitimate request.
839
930
  */
840
- function canonicalSystemPrefixLength(system) {
841
- if (systemBlockText(system[1]) === IDENTITY_TEXT) {
842
- return CANONICAL_SYSTEM_BLOCKS;
931
+ function canonicalSystemPrefixLength(system, billingSuppressed, identitySuppressed) {
932
+ let index = 0;
933
+ if (!billingSuppressed) {
934
+ if (!isBillingBlockText(systemBlockText(system[index])))
935
+ fail();
936
+ index += 1;
843
937
  }
844
- if (systemBlockText(system[0]) === IDENTITY_TEXT) {
845
- return CANONICAL_SYSTEM_BLOCKS_WITHOUT_BILLING;
938
+ if (identitySuppressed) {
939
+ // The identity text cannot appear ANYWHERE in a body built with the seam
940
+ // active: `buildCanonicalSystem` drops a caller block equal to it
941
+ // unconditionally, and merging joins with `\n`, so no merged run can equal
942
+ // it either. Absence is therefore checkable, which is what refutes a claim
943
+ // of suppression made over a body that still carries the block.
944
+ //
945
+ // No mirror check exists for billing: a caller block may legitimately begin
946
+ // with the billing header text, so its presence proves nothing.
947
+ if (system.some((block) => systemBlockText(block) === IDENTITY_TEXT)) {
948
+ fail();
949
+ }
950
+ return index;
846
951
  }
847
- return fail();
952
+ if (systemBlockText(system[index]) !== IDENTITY_TEXT)
953
+ fail();
954
+ return index + 1;
848
955
  }
849
956
  function parseBody(value) {
850
957
  let parsed;
@@ -916,6 +1023,12 @@ function evidenceRequest(input, callerModel) {
916
1023
  request.capabilities = input.capabilities;
917
1024
  if (input.betaOverrides !== undefined)
918
1025
  request.betaOverrides = input.betaOverrides;
1026
+ // The body builder owns the thinking-block allowlist, so the seam flag has to
1027
+ // reach it. It is forwarded only when the caller stated it, keeping the
1028
+ // normalized request shape identical for every request that ignores the seam.
1029
+ if (input.preserveThinkingBlockCacheControl !== undefined)
1030
+ request.preserveThinkingBlockCacheControl =
1031
+ input.preserveThinkingBlockCacheControl;
919
1032
  if (input.thinking !== undefined)
920
1033
  request.thinking = input.thinking;
921
1034
  if (input.effort !== undefined)
@@ -1036,7 +1149,7 @@ export async function buildClaudeCodeRequest(input, profile = CLAUDE_CODE_2_1_19
1036
1149
  });
1037
1150
  const billing = await createBillingBlock(fingerprintText(validated.source), effectiveProfile.cliVersion, validated.crypto);
1038
1151
  const metadata = buildCorrelatedMetadata(identity, validated.source.metadata, validated.source.metadataOverrides);
1039
- const system = buildCanonicalSystem(validated.source.system, billing, identity, validated.suppressBillingBlock);
1152
+ const system = buildCanonicalSystem(validated.source.system, billing, identity, validated.suppressBillingBlock, validated.suppressIdentityBlock);
1040
1153
  const canonicalBody = buildCanonicalBody(evidenceRequest(validated.source, validated.source.model), effectiveModel, system, metadata, effectiveProfile);
1041
1154
  const composedBetas = composeBetasWithAudit({
1042
1155
  rawModel: validated.source.model,
@@ -1089,10 +1202,12 @@ export async function buildClaudeCodeRequest(input, profile = CLAUDE_CODE_2_1_19
1089
1202
  // The canonical system merges adjacent caller blocks and drops any
1090
1203
  // block equal to the identity text, so only the emitted count keeps
1091
1204
  // `systemBlockCount === body.system.length - <canonical>` true.
1205
+ // Symmetric arithmetic over the two suppression seams: each canonical
1206
+ // block that survived costs one slot. A single constant cannot express
1207
+ // the empty prefix both seams together produce.
1092
1208
  emittedSystemBlockCount: system.length -
1093
- (validated.suppressBillingBlock
1094
- ? CANONICAL_SYSTEM_BLOCKS_WITHOUT_BILLING
1095
- : CANONICAL_SYSTEM_BLOCKS),
1209
+ (validated.suppressBillingBlock ? 0 : 1) -
1210
+ (validated.suppressIdentityBlock ? 0 : 1),
1096
1211
  // Emitted only for the opted-in policy, so evidence for every other
1097
1212
  // request keeps the shape it had before the seam existed.
1098
1213
  ...(validated.extraHeaderPolicy === "dropConflicting"
@@ -1109,6 +1224,20 @@ export async function buildClaudeCodeRequest(input, profile = CLAUDE_CODE_2_1_19
1109
1224
  ...(validated.suppressBillingBlock
1110
1225
  ? { billingBlockSuppressed: true }
1111
1226
  : {}),
1227
+ // Emitted only when the identity block was actually removed, on the
1228
+ // same terms as `billingBlockSuppressed`: the parser reads both flags
1229
+ // to know the length of the canonical prefix it must verify.
1230
+ ...(validated.suppressIdentityBlock
1231
+ ? { identityBlockSuppressed: true }
1232
+ : {}),
1233
+ // Emitted only when the seam was active AND a reasoning block actually
1234
+ // carried the marker. Opting in without using it records nothing, so
1235
+ // the audit states what happened on the wire rather than what the
1236
+ // caller was permitted to do.
1237
+ ...(validated.preserveThinkingBlockCacheControl &&
1238
+ hasThinkingBlockCacheControl(canonicalBody["messages"])
1239
+ ? { thinkingBlockCacheControlPreserved: true }
1240
+ : {}),
1112
1241
  }, validated.crypto);
1113
1242
  return deepFreeze({
1114
1243
  url: effectiveProfile.endpoint,
@@ -1148,6 +1277,15 @@ export function parseBuiltClaudeCodeRequest(value, profile = CLAUDE_CODE_2_1_195
1148
1277
  const parsedBody = parseBody(body);
1149
1278
  const headers = parseHeaders(ownValue(value, "headers"));
1150
1279
  const evidence = parseEvidence(ownValue(value, "evidence"));
1280
+ // Reading evidence is not trusting evidence. A claim that the seam
1281
+ // preserved a marker is confirmed against the body, and it is confirmed
1282
+ // HERE — before the byte-length and digest checks — so that a forgery which
1283
+ // is byte-length preserving and evidence-self-consistent is refused by the
1284
+ // structural check rather than incidentally by arithmetic.
1285
+ if (evidence.thinkingBlockCacheControlPreserved === true &&
1286
+ !hasThinkingBlockCacheControl(parsedBody["messages"])) {
1287
+ fail();
1288
+ }
1151
1289
  const sessionId = headerValue(headers, "x-claude-code-session-id");
1152
1290
  const additionalHeaders = splitDynamicAndExtraHeaders(headers);
1153
1291
  const expectedHeaders = buildOrderedHeaders({
@@ -1186,7 +1324,7 @@ export function parseBuiltClaudeCodeRequest(value, profile = CLAUDE_CODE_2_1_195
1186
1324
  evidence.systemBlockCount !==
1187
1325
  (Array.isArray(parsedBody["system"])
1188
1326
  ? parsedBody["system"].length -
1189
- canonicalSystemPrefixLength(parsedBody["system"])
1327
+ canonicalSystemPrefixLength(parsedBody["system"], evidence.billingBlockSuppressed === true, evidence.identityBlockSuppressed === true)
1190
1328
  : -1)) {
1191
1329
  fail();
1192
1330
  }