Skip to content

Commit 2ada8e7

Browse files
committed
Release CodeTruss CLI v0.2.63
1 parent 61501fe commit 2ada8e7

13 files changed

Lines changed: 227 additions & 20 deletions

CHANGELOG.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
CodeTruss CLI follows semantic versioning. Release artifacts and their SHA-256
44
checksums are published at <https://codetruss.com/downloads/codetruss-cli-latest.json>.
55

6-
The current public release is [v0.2.62 on GitHub](https://github.com/CodeTruss/codetruss-cli/releases/tag/v0.2.62),
6+
The current public release is [v0.2.63 on GitHub](https://github.com/CodeTruss/codetruss-cli/releases/tag/v0.2.63),
77
distributed from <https://codetruss.com/downloads/codetruss-cli-latest.json>.
88
npm publication is a separate, manually dispatched step, so the npm `latest`
99
tag can trail the website and the GitHub release; the dispatch for this
@@ -13,6 +13,18 @@ were superseded before distribution.
1313

1414
## Unreleased
1515

16+
## 0.2.63 — 2026-08-09
17+
18+
- **`codetruss sync` no longer relabels who produced a receipt.** The sync
19+
envelope used to overwrite the receipt's signing identity with whatever key
20+
ran the sync, so a teammate exporting your receipt made the hosted record
21+
claim they signed it. The producer's public key and fingerprint now travel
22+
unchanged, and the exporting key is named separately in
23+
`evidence.exporter`; the envelope signature stays the exporter's. The
24+
hosted side pins API credentials to the exporting key (the machine actually
25+
syncing) and displays the producer as the signer, with the exporter shown
26+
when the two differ.
27+
1628
## 0.2.62 — 2026-08-09
1729

1830
- **The comment analyzer learned the difference between deferring with a

packages/cli/CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,18 @@ checksums are published at <https://codetruss.com/downloads/codetruss-cli-latest
55

66
## Unreleased
77

8+
## 0.2.63 — 2026-08-09
9+
10+
- **`codetruss sync` no longer relabels who produced a receipt.** The sync
11+
envelope used to overwrite the receipt's signing identity with whatever key
12+
ran the sync, so a teammate exporting your receipt made the hosted record
13+
claim they signed it. The producer's public key and fingerprint now travel
14+
unchanged, and the exporting key is named separately in
15+
`evidence.exporter`; the envelope signature stays the exporter's. The
16+
hosted side pins API credentials to the exporting key (the machine actually
17+
syncing) and displays the producer as the signer, with the exporter shown
18+
when the two differ.
19+
820
## 0.2.62 — 2026-08-09
921

1022
- **The comment analyzer learned the difference between deferring with a

packages/cli/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@codetruss/cli",
3-
"version": "0.2.62",
3+
"version": "0.2.63",
44
"description": "Local-first scope, quality, and verification receipts for coding agents",
55
"license": "SEE LICENSE IN LICENSE",
66
"type": "module",

packages/cli/src/receipt-store.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,10 +183,17 @@ export async function createSyncEnvelope(receipt: Receipt): Promise<SyncEnvelope
183183
else delete synced.analyzers.rejectedSuppressions
184184
}
185185
synced.verifications = synced.verifications.map((item) => ({ ...item, command: '[redacted for sync]', output: '' }))
186+
// The producer's signing identity stays exactly as the receipt recorded it;
187+
// the exporting key signs the envelope and is named separately. Overwriting
188+
// the producer fields here (the old behavior) relabeled a teammate's receipt
189+
// as whoever ran `codetruss sync`.
190+
const producer = receipt.evidence.publicKey && receipt.evidence.keyFingerprint
191+
? { publicKey: receipt.evidence.publicKey, keyFingerprint: receipt.evidence.keyFingerprint }
192+
: { publicKey: key.publicKey, keyFingerprint: key.fingerprint }
186193
synced.evidence = {
187194
patchSha256: receipt.evidence.patchSha256,
188-
publicKey: key.publicKey,
189-
keyFingerprint: key.fingerprint,
195+
...producer,
196+
exporter: { publicKey: key.publicKey, keyFingerprint: key.fingerprint },
190197
}
191198
synced.coverageNotes = [
192199
...synced.coverageNotes,

packages/cli/src/types.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,10 @@ export interface Receipt {
250250
coverageNotes: string[]
251251
verdict: Verdict
252252
reasons: string[]
253-
evidence: { markdownSha256?: string; patchFile?: string; patchSha256?: string; signatureFile?: string; publicKey?: string; keyFingerprint?: string }
253+
/** `exporter` appears only on hosted-sync copies: the key that signed the
254+
* sync envelope, kept separate so exporting a teammate's receipt can never
255+
* relabel who produced it (`publicKey`/`keyFingerprint` stay the producer's). */
256+
evidence: { markdownSha256?: string; patchFile?: string; patchSha256?: string; signatureFile?: string; publicKey?: string; keyFingerprint?: string; exporter?: { publicKey: string; keyFingerprint: string } }
254257
}
255258

256259
export interface ReviewOptions {

packages/cli/test/receipt.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -615,8 +615,10 @@ describe('signed receipts', () => {
615615
suggestion: 'Extract [redacted unrelated path] and src/changed.ts into one module.',
616616
filePath: 'src/changed.ts', line: 3, impactScore: 55, effort: 'medium',
617617
}])
618-
expect(Object.keys(synced.evidence).sort()).toEqual(['keyFingerprint', 'patchSha256', 'publicKey'])
619-
expect(verifyBytes(envelope.signedReceipt, synced.evidence.publicKey!, envelope.signature)).toBe(true)
618+
expect(Object.keys(synced.evidence).sort()).toEqual(['exporter', 'keyFingerprint', 'patchSha256', 'publicKey'])
619+
// The envelope is signed by the exporting key, named separately so the
620+
// producer identity fields cannot be relabeled by whoever runs the sync.
621+
expect(verifyBytes(envelope.signedReceipt, synced.evidence.exporter!.publicKey, envelope.signature)).toBe(true)
620622
expect(envelope.signedReceipt).not.toContain('secret prompt')
621623
expect(envelope.signedReceipt).not.toContain('sensitive output')
622624
expect(envelope.signedReceipt).not.toContain('"command": "test"')
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
{
2+
"$schema": "https://cyclonedx.org/schema/bom-1.6.schema.json",
3+
"bomFormat": "CycloneDX",
4+
"serialNumber": "urn:uuid:5587fc86-bda2-5368-8379-3f323f53b318",
5+
"specVersion": "1.6",
6+
"version": 1,
7+
"metadata": {
8+
"component": {
9+
"type": "application",
10+
"bom-ref": "pkg:npm/%40codetruss/cli@0.2.63",
11+
"name": "@codetruss/cli",
12+
"version": "0.2.63",
13+
"description": "Local-first scope, quality, and verification receipts for coding agents",
14+
"licenses": [
15+
{
16+
"license": {
17+
"name": "CodeTruss CLI Proprietary License"
18+
}
19+
}
20+
],
21+
"purl": "pkg:npm/%40codetruss/cli@0.2.63"
22+
},
23+
"properties": [
24+
{
25+
"name": "codetruss:distribution",
26+
"value": "single-file JavaScript bundle"
27+
},
28+
{
29+
"name": "codetruss:runtimeDependencies",
30+
"value": "0"
31+
}
32+
]
33+
},
34+
"components": [
35+
{
36+
"type": "library",
37+
"bom-ref": "pkg:npm/%40codetruss/analyzer-engine@0.1.0",
38+
"name": "@codetruss/analyzer-engine",
39+
"version": "0.1.0",
40+
"licenses": [
41+
{
42+
"license": {
43+
"name": "CodeTruss CLI Proprietary License"
44+
}
45+
}
46+
],
47+
"purl": "pkg:npm/%40codetruss/analyzer-engine@0.1.0",
48+
"properties": [
49+
{
50+
"name": "codetruss:bundled",
51+
"value": "true"
52+
}
53+
]
54+
},
55+
{
56+
"type": "library",
57+
"bom-ref": "pkg:npm/balanced-match@4.0.4",
58+
"name": "balanced-match",
59+
"version": "4.0.4",
60+
"licenses": [
61+
{
62+
"license": {
63+
"id": "MIT"
64+
}
65+
}
66+
],
67+
"purl": "pkg:npm/balanced-match@4.0.4",
68+
"properties": [
69+
{
70+
"name": "codetruss:bundled",
71+
"value": "true"
72+
}
73+
]
74+
},
75+
{
76+
"type": "library",
77+
"bom-ref": "pkg:npm/brace-expansion@5.0.9",
78+
"name": "brace-expansion",
79+
"version": "5.0.9",
80+
"licenses": [
81+
{
82+
"license": {
83+
"id": "MIT"
84+
}
85+
}
86+
],
87+
"purl": "pkg:npm/brace-expansion@5.0.9",
88+
"properties": [
89+
{
90+
"name": "codetruss:bundled",
91+
"value": "true"
92+
}
93+
]
94+
},
95+
{
96+
"type": "library",
97+
"bom-ref": "pkg:npm/minimatch@10.2.6",
98+
"name": "minimatch",
99+
"version": "10.2.6",
100+
"licenses": [
101+
{
102+
"license": {
103+
"id": "BlueOak-1.0.0"
104+
}
105+
}
106+
],
107+
"purl": "pkg:npm/minimatch@10.2.6",
108+
"properties": [
109+
{
110+
"name": "codetruss:bundled",
111+
"value": "true"
112+
}
113+
]
114+
},
115+
{
116+
"type": "library",
117+
"bom-ref": "pkg:npm/yaml@2.9.0",
118+
"name": "yaml",
119+
"version": "2.9.0",
120+
"licenses": [
121+
{
122+
"license": {
123+
"id": "ISC"
124+
}
125+
}
126+
],
127+
"purl": "pkg:npm/yaml@2.9.0",
128+
"properties": [
129+
{
130+
"name": "codetruss:bundled",
131+
"value": "true"
132+
}
133+
]
134+
}
135+
],
136+
"dependencies": [
137+
{
138+
"ref": "pkg:npm/%40codetruss/analyzer-engine@0.1.0",
139+
"dependsOn": []
140+
},
141+
{
142+
"ref": "pkg:npm/%40codetruss/cli@0.2.63",
143+
"dependsOn": [
144+
"pkg:npm/%40codetruss/analyzer-engine@0.1.0",
145+
"pkg:npm/minimatch@10.2.6",
146+
"pkg:npm/yaml@2.9.0"
147+
]
148+
},
149+
{
150+
"ref": "pkg:npm/balanced-match@4.0.4",
151+
"dependsOn": []
152+
},
153+
{
154+
"ref": "pkg:npm/brace-expansion@5.0.9",
155+
"dependsOn": [
156+
"pkg:npm/balanced-match@4.0.4"
157+
]
158+
},
159+
{
160+
"ref": "pkg:npm/minimatch@10.2.6",
161+
"dependsOn": [
162+
"pkg:npm/brace-expansion@5.0.9"
163+
]
164+
},
165+
{
166+
"ref": "pkg:npm/yaml@2.9.0",
167+
"dependsOn": []
168+
}
169+
]
170+
}
966 KB
Binary file not shown.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
cc63fdbc491b2ef7ac579bd721488e4a876287930995a0b347b74024180be748 codetruss-cli-0.2.63.tgz
Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
{
22
"name": "@codetruss/cli",
3-
"version": "0.2.62",
4-
"url": "/downloads/codetruss-cli-0.2.62.tgz",
3+
"version": "0.2.63",
4+
"url": "/downloads/codetruss-cli-0.2.63.tgz",
55
"latestUrl": "/downloads/codetruss-cli-latest.tgz",
6-
"sha256": "35ad8c84288014ced4a184e702480bd478310e9c990fe18498c0e1038388e8a9",
7-
"sbomUrl": "/downloads/codetruss-cli-0.2.62.sbom.cdx.json",
8-
"sbomSha256": "4d5b7fb2eeaf1a17bb9f9891161b098f5a6dffd53fa7650c30de045d7c456284",
6+
"sha256": "cc63fdbc491b2ef7ac579bd721488e4a876287930995a0b347b74024180be748",
7+
"sbomUrl": "/downloads/codetruss-cli-0.2.63.sbom.cdx.json",
8+
"sbomSha256": "6e89ef8f91c0d0cdc893845876af3b1fa225744a89f85c2eac2f5263c7d6dc4a",
99
"node": ">=20.9.0",
1010
"repository": "https://github.com/CodeTruss/codetruss-cli",
11-
"releaseUrl": "https://github.com/CodeTruss/codetruss-cli/releases/tag/v0.2.62",
12-
"attestationCommand": "gh attestation verify codetruss-cli-0.2.62.tgz --repo CodeTruss/codetruss-cli"
11+
"releaseUrl": "https://github.com/CodeTruss/codetruss-cli/releases/tag/v0.2.63",
12+
"attestationCommand": "gh attestation verify codetruss-cli-0.2.63.tgz --repo CodeTruss/codetruss-cli"
1313
}

0 commit comments

Comments
 (0)