@hraness/message-like-me 0.8.3 → 0.8.5

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
@@ -1,5 +1,37 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.8.5 (2026-09-06)
4
+
5
+ - Make the exact public `@hraness/message-like-me@0.8.5` npm package the
6
+ supported default install, with the same reviewed bytes mirrored in the
7
+ immutable GitHub Release.
8
+ - Give each command one scoped owner for its private store, source reads and
9
+ artifact publication. Typed failures retain the existing CLI diagnostics and
10
+ exit codes; store transactions remain synchronous.
11
+ - Publish private exports through exclusive staging and no-overwrite paths.
12
+ Reconcile receipt failures before cleanup, retain files whose ownership or
13
+ publication status cannot be proved, and bound cleanup reads even if a file
14
+ grows during inspection.
15
+ - Bundle Effect 3.22.1 inside the CLI. Public protocol entrypoints remain pure,
16
+ preserve their declarations, and require no new consumer dependency.
17
+ - Bound private JSON read allocation to the admitted file size, including
18
+ short reads and files that grow during inspection.
19
+
20
+ ## 0.8.4 (2026-09-06)
21
+
22
+ - Make the exact public `@hraness/message-like-me@0.8.4` npm package the
23
+ supported default install, with the same reviewed bytes mirrored in the
24
+ immutable GitHub Release. The product is unchanged from 0.8.3.
25
+ - Let the GitHub Release writer wait briefly for its freshly created draft
26
+ instead of failing on the first inventory read, so an automated release no
27
+ longer needs a manual rerun.
28
+ - Fetch the release tag with one more commit of history than the admitted
29
+ workflow range and prove the production commit is its ancestor before the
30
+ leased fast-forward, so Git attributes the push instead of reporting a forced
31
+ update. Every job after promotion path selection carries an explicit
32
+ skip-aware condition, and a terminal admission fails unless verification and
33
+ provider admission both succeeded.
34
+
3
35
  ## 0.8.3 (2026-09-06)
4
36
 
5
37
  - Let Beeper users bring a finished private Wrench bundle into the same local
package/README.md CHANGED
@@ -43,7 +43,7 @@ Message Like Me requires Bun 1.3.14 or newer. Install the exact public npm
43
43
  package, then install both bundled Agent Skills:
44
44
 
45
45
  ```sh
46
- bun add --global @hraness/message-like-me@0.8.3
46
+ bun add --global @hraness/message-like-me@0.8.5
47
47
  messagelikeme skill install
48
48
  ```
49
49
 
@@ -1,30 +1,486 @@
1
1
  // @bun
2
2
  import {
3
- AGENTIC_MESSAGING_V1_LIMITS,
4
- AGENTIC_MESSAGING_V1_SCHEMA_VERSION,
5
- AGENT_MESSAGE_AUDIT_V1_FORMAT,
6
- AGENT_MESSAGE_DRAFT_V1_FORMAT,
7
- AGENT_MESSAGE_HANDOFF_REQUEST_V1_FORMAT,
8
- AGENT_MESSAGE_HANDOFF_V1_FORMAT,
9
- AgenticMessagingV1ContractError,
10
- WRENCH_MESSAGING_CONTEXT_BINDING_V1_CONTRACT_DESCRIPTOR,
11
- WRENCH_MESSAGING_CONTEXT_BINDING_V1_CONTRACT_HASH,
12
- WRENCH_MESSAGING_CONTEXT_BINDING_V1_CONTRACT_ID,
13
- WRENCH_MESSAGING_CONTEXT_BINDING_V1_FORMAT,
14
- WRENCH_MESSAGING_RECEIPT_BINDING_V1_CONTRACT_DESCRIPTOR,
15
- WRENCH_MESSAGING_RECEIPT_BINDING_V1_CONTRACT_HASH,
16
- WRENCH_MESSAGING_RECEIPT_BINDING_V1_CONTRACT_ID,
17
- WRENCH_MESSAGING_RECEIPT_BINDING_V1_FORMAT,
18
- agentMessageRouteCandidateId,
19
- createAgentMessageHandoffV1,
20
- parseAgentMessageDraftV1,
21
- parseAgentMessageHandoffRequestV1,
22
- parseAgentMessageHandoffV1,
23
- parseWrenchMessagingContextBindingV1,
24
- parseWrenchMessagingReceiptBindingV1,
25
- wrenchMessagingTurnDigestV1
26
- } from "./cli-d7qv38ab.js";
27
- import"./cli-ththzwja.js";
3
+ canonicalJson,
4
+ sha256
5
+ } from "./index-y1aec88m.js";
6
+
7
+ // src/agentic-messaging-v1.ts
8
+ import { types as nodeTypes } from "util";
9
+ var AGENTIC_MESSAGING_V1_SCHEMA_VERSION = 1;
10
+ var AGENT_MESSAGE_DRAFT_V1_FORMAT = "message-like-me.agent-message-draft";
11
+ var AGENT_MESSAGE_HANDOFF_REQUEST_V1_FORMAT = "message-like-me.agent-message-handoff-request";
12
+ var AGENT_MESSAGE_HANDOFF_V1_FORMAT = "message-like-me.agent-message-handoff";
13
+ var AGENT_MESSAGE_AUDIT_V1_FORMAT = "message-like-me.agent-message-handoff-audit";
14
+ var WRENCH_MESSAGING_CONTEXT_BINDING_V1_FORMAT = "wrench.messaging-context-binding";
15
+ var WRENCH_MESSAGING_RECEIPT_BINDING_V1_FORMAT = "wrench.messaging-receipt-binding";
16
+ var WRENCH_MESSAGING_CONTEXT_BINDING_V1_CONTRACT_ID = "wrench.messaging-context-binding.v1";
17
+ var WRENCH_MESSAGING_CONTEXT_BINDING_V1_CONTRACT_HASH = "5e64da6a3d826e7f6fa3db7dca0a4ba92c10cfb784981e71a25aed9513a5c687";
18
+ var WRENCH_MESSAGING_CONTEXT_BINDING_V1_CONTRACT_DESCRIPTOR = Object.freeze({
19
+ contractId: WRENCH_MESSAGING_CONTEXT_BINDING_V1_CONTRACT_ID,
20
+ fields: Object.freeze([
21
+ "schemaVersion:1",
22
+ "format:wrench.messaging-context-binding",
23
+ "contractId:wrench.messaging-context-binding.v1",
24
+ "contractHash:sha256",
25
+ "routeRef:opaque",
26
+ "contextRef:opaque",
27
+ "exactDataRevision:sha256",
28
+ "latestMessageRevision:sha256",
29
+ "validatedAt:rfc3339",
30
+ "expiresAt:rfc3339"
31
+ ]),
32
+ format: "wrench.messaging-contract-descriptor",
33
+ schemaVersion: 1
34
+ });
35
+ var WRENCH_MESSAGING_RECEIPT_BINDING_V1_CONTRACT_ID = "wrench.messaging-receipt-binding.v1";
36
+ var WRENCH_MESSAGING_RECEIPT_BINDING_V1_CONTRACT_HASH = "7f6cf724f0200b2399e4f4641c637b20b48914fc5c9b13755127a8ec69fe66f4";
37
+ var WRENCH_MESSAGING_RECEIPT_BINDING_V1_CONTRACT_DESCRIPTOR = Object.freeze({
38
+ contractId: WRENCH_MESSAGING_RECEIPT_BINDING_V1_CONTRACT_ID,
39
+ fields: Object.freeze([
40
+ "schemaVersion:1",
41
+ "format:wrench.messaging-receipt-binding",
42
+ "contractId:wrench.messaging-receipt-binding.v1",
43
+ "contractHash:sha256",
44
+ "clientIntentSha256:sha256",
45
+ "routeRefSha256:sha256",
46
+ "contextRefSha256:sha256",
47
+ "turnDigest:sha256",
48
+ "previewDigest:sha256",
49
+ "runId:opaque",
50
+ "state:submitted|failed|partial|indeterminate",
51
+ "partCount:uint",
52
+ "provenPartCount:uint",
53
+ "receiptSha256:sha256",
54
+ "recordedAt:rfc3339"
55
+ ]),
56
+ format: "wrench.messaging-contract-descriptor",
57
+ schemaVersion: 1
58
+ });
59
+ var AGENTIC_MESSAGING_V1_LIMITS = Object.freeze({
60
+ bubbles: 8,
61
+ bubbleBytes: 8 * 1024,
62
+ totalBubbleBytes: 32 * 1024,
63
+ identifierBytes: 1024,
64
+ privateJsonBytes: 128 * 1024,
65
+ handoffLifetimeMilliseconds: 10 * 60 * 1000,
66
+ maximumContextLifetimeMilliseconds: 24 * 60 * 60 * 1000
67
+ });
68
+
69
+ class AgenticMessagingV1ContractError extends TypeError {
70
+ code = "agentic-messaging-v1-contract";
71
+ constructor(message, options) {
72
+ super(message, options);
73
+ this.name = "AgenticMessagingV1ContractError";
74
+ }
75
+ }
76
+ function fail(message) {
77
+ throw new AgenticMessagingV1ContractError(message);
78
+ }
79
+ function object(value, label) {
80
+ if (value === null || typeof value !== "object" || Array.isArray(value) || nodeTypes.isProxy(value) || Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null)
81
+ return fail(`${label} must be a plain object`);
82
+ for (const key of Reflect.ownKeys(value)) {
83
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
84
+ if (typeof key !== "string" || descriptor === undefined || descriptor.enumerable !== true || !("value" in descriptor))
85
+ return fail(`${label} must contain only enumerable string data properties`);
86
+ }
87
+ return value;
88
+ }
89
+ function exactKeys(value, keys, label) {
90
+ const expected = [...keys].sort();
91
+ const observed = Reflect.ownKeys(value).map(String).sort();
92
+ if (expected.length !== observed.length || observed.some((key, index) => key !== expected[index])) {
93
+ fail(`${label} must contain exactly: ${keys.join(", ")}`);
94
+ }
95
+ }
96
+ function denseArray(value, label, minimum, maximum) {
97
+ if (!Array.isArray(value) || nodeTypes.isProxy(value) || Object.getPrototypeOf(value) !== Array.prototype || value.length < minimum || value.length > maximum)
98
+ return fail(`${label} must contain ${minimum} through ${maximum} items`);
99
+ const keys = Reflect.ownKeys(value);
100
+ const expected = new Set(["length", ...Array.from({ length: value.length }, (_item, index) => String(index))]);
101
+ for (const key of keys) {
102
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
103
+ if (typeof key !== "string" || !expected.has(key) || descriptor === undefined || !("value" in descriptor) || key !== "length" && descriptor.enumerable !== true)
104
+ return fail(`${label} must be a dense array of data properties`);
105
+ }
106
+ if (keys.length !== expected.size)
107
+ return fail(`${label} must be a dense array of data properties`);
108
+ return value;
109
+ }
110
+ function utf8Bytes(value) {
111
+ return new TextEncoder().encode(value).byteLength;
112
+ }
113
+ function isWellFormedUnicode(value) {
114
+ for (let index = 0;index < value.length; index += 1) {
115
+ const unit = value.charCodeAt(index);
116
+ if (unit >= 55296 && unit <= 56319) {
117
+ const next = value.charCodeAt(index + 1);
118
+ if (!(next >= 56320 && next <= 57343))
119
+ return false;
120
+ index += 1;
121
+ } else if (unit >= 56320 && unit <= 57343) {
122
+ return false;
123
+ }
124
+ }
125
+ return true;
126
+ }
127
+ function boundedText(value, label, maximum) {
128
+ if (typeof value !== "string" || !isWellFormedUnicode(value) || utf8Bytes(value) > maximum || value.includes("\x00"))
129
+ return fail(`${label} must be well-formed NUL-free text within ${maximum} UTF-8 bytes`);
130
+ return value;
131
+ }
132
+ function identifier(value, label, maximum = AGENTIC_MESSAGING_V1_LIMITS.identifierBytes) {
133
+ const result = boundedText(value, label, maximum);
134
+ if (result.length === 0 || /\p{Cc}|\p{Zl}|\p{Zp}/u.test(result) || result !== result.trim()) {
135
+ return fail(`${label} must be a bounded opaque identifier without controls or surrounding space`);
136
+ }
137
+ return result;
138
+ }
139
+ function digest(value, label) {
140
+ const result = boundedText(value, label, 64);
141
+ if (!/^[a-f0-9]{64}$/u.test(result))
142
+ return fail(`${label} must be lowercase SHA-256`);
143
+ return result;
144
+ }
145
+ function routeCandidateId(value, label) {
146
+ const result = identifier(value, label, 70);
147
+ if (!/^route_[a-f0-9]{64}$/u.test(result)) {
148
+ return fail(`${label} must be a canonical source-conversation route ID`);
149
+ }
150
+ return result;
151
+ }
152
+ function timestamp(value, label) {
153
+ const result = boundedText(value, label, 64);
154
+ const parsed = new Date(result);
155
+ if (!Number.isFinite(parsed.getTime()) || parsed.toISOString() !== result) {
156
+ return fail(`${label} must be a canonical UTC timestamp`);
157
+ }
158
+ return result;
159
+ }
160
+ function bubble(value, label) {
161
+ const record = object(value, label);
162
+ exactKeys(record, ["id", "text", "replyToRef"], label);
163
+ const text = boundedText(record.text, `${label}.text`, AGENTIC_MESSAGING_V1_LIMITS.bubbleBytes);
164
+ if (text.trim().length === 0 || /[\u0000-\u0009\u000b-\u001f\u007f-\u009f]/u.test(text)) {
165
+ return fail(`${label}.text must contain visible text and no unsupported controls`);
166
+ }
167
+ return Object.freeze({
168
+ id: identifier(record.id, `${label}.id`, 128),
169
+ text,
170
+ replyToRef: record.replyToRef === null ? null : identifier(record.replyToRef, `${label}.replyToRef`)
171
+ });
172
+ }
173
+ function bubbles(value, label) {
174
+ const result = denseArray(value, label, 1, AGENTIC_MESSAGING_V1_LIMITS.bubbles).map((item, index) => bubble(item, `${label}[${index}]`));
175
+ if (new Set(result.map(({ id }) => id)).size !== result.length)
176
+ fail(`${label} repeats a bubble ID`);
177
+ if (result.reduce((sum, item) => sum + utf8Bytes(item.text), 0) > AGENTIC_MESSAGING_V1_LIMITS.totalBubbleBytes) {
178
+ fail(`${label} exceeds the total text bound`);
179
+ }
180
+ return Object.freeze(result);
181
+ }
182
+ function profileState(value, label) {
183
+ if (value !== "current" && value !== "missing" && value !== "stale") {
184
+ return fail(`${label} must be current, missing, or stale`);
185
+ }
186
+ return value;
187
+ }
188
+ function handoffCore(value) {
189
+ return value;
190
+ }
191
+ function handoffDigest(value) {
192
+ return sha256(canonicalJson(handoffCore(value)));
193
+ }
194
+ function parseWrenchMessagingContextBindingV1(value) {
195
+ const record = object(value, "Wrench messaging context binding");
196
+ exactKeys(record, [
197
+ "schemaVersion",
198
+ "format",
199
+ "contractId",
200
+ "contractHash",
201
+ "routeRef",
202
+ "contextRef",
203
+ "exactDataRevision",
204
+ "latestMessageRevision",
205
+ "validatedAt",
206
+ "expiresAt"
207
+ ], "Wrench messaging context binding");
208
+ if (record.schemaVersion !== AGENTIC_MESSAGING_V1_SCHEMA_VERSION || record.format !== WRENCH_MESSAGING_CONTEXT_BINDING_V1_FORMAT)
209
+ return fail("Wrench messaging context binding has the wrong schemaVersion or format");
210
+ if (record.contractId !== WRENCH_MESSAGING_CONTEXT_BINDING_V1_CONTRACT_ID || record.contractHash !== WRENCH_MESSAGING_CONTEXT_BINDING_V1_CONTRACT_HASH)
211
+ return fail("Wrench messaging context binding has an unsupported contract identity");
212
+ const validatedAt = timestamp(record.validatedAt, "Wrench messaging context binding.validatedAt");
213
+ const expiresAt = timestamp(record.expiresAt, "Wrench messaging context binding.expiresAt");
214
+ const lifetime = Date.parse(expiresAt) - Date.parse(validatedAt);
215
+ if (lifetime <= 0 || lifetime > AGENTIC_MESSAGING_V1_LIMITS.maximumContextLifetimeMilliseconds) {
216
+ return fail("Wrench messaging context binding has an invalid lifetime");
217
+ }
218
+ return Object.freeze({
219
+ schemaVersion: AGENTIC_MESSAGING_V1_SCHEMA_VERSION,
220
+ format: WRENCH_MESSAGING_CONTEXT_BINDING_V1_FORMAT,
221
+ contractId: WRENCH_MESSAGING_CONTEXT_BINDING_V1_CONTRACT_ID,
222
+ contractHash: WRENCH_MESSAGING_CONTEXT_BINDING_V1_CONTRACT_HASH,
223
+ routeRef: identifier(record.routeRef, "Wrench messaging context binding.routeRef"),
224
+ contextRef: identifier(record.contextRef, "Wrench messaging context binding.contextRef"),
225
+ exactDataRevision: digest(record.exactDataRevision, "Wrench messaging context binding.exactDataRevision"),
226
+ latestMessageRevision: digest(record.latestMessageRevision, "Wrench messaging context binding.latestMessageRevision"),
227
+ validatedAt,
228
+ expiresAt
229
+ });
230
+ }
231
+ function parseAgentMessageDraftV1(value) {
232
+ const record = object(value, "Agent message draft");
233
+ exactKeys(record, ["schemaVersion", "format", "bubbles"], "Agent message draft");
234
+ if (record.schemaVersion !== AGENTIC_MESSAGING_V1_SCHEMA_VERSION || record.format !== AGENT_MESSAGE_DRAFT_V1_FORMAT)
235
+ return fail("Agent message draft has the wrong schemaVersion or format");
236
+ return Object.freeze({
237
+ schemaVersion: AGENTIC_MESSAGING_V1_SCHEMA_VERSION,
238
+ format: AGENT_MESSAGE_DRAFT_V1_FORMAT,
239
+ bubbles: bubbles(record.bubbles, "Agent message draft.bubbles")
240
+ });
241
+ }
242
+ function parseAgentMessageHandoffRequestV1(value) {
243
+ const record = object(value, "Agent message handoff request");
244
+ exactKeys(record, ["schemaVersion", "format", "routeCandidateId"], "Agent message handoff request");
245
+ if (record.schemaVersion !== AGENTIC_MESSAGING_V1_SCHEMA_VERSION || record.format !== AGENT_MESSAGE_HANDOFF_REQUEST_V1_FORMAT)
246
+ return fail("Agent message handoff request has the wrong schemaVersion or format");
247
+ return Object.freeze({
248
+ schemaVersion: AGENTIC_MESSAGING_V1_SCHEMA_VERSION,
249
+ format: AGENT_MESSAGE_HANDOFF_REQUEST_V1_FORMAT,
250
+ routeCandidateId: routeCandidateId(record.routeCandidateId, "Agent message handoff request.routeCandidateId")
251
+ });
252
+ }
253
+ function createAgentMessageHandoffV1(input) {
254
+ const context = parseWrenchMessagingContextBindingV1(input.wrenchContext);
255
+ const draft = parseAgentMessageDraftV1(input.draft);
256
+ const core = Object.freeze({
257
+ schemaVersion: AGENTIC_MESSAGING_V1_SCHEMA_VERSION,
258
+ format: AGENT_MESSAGE_HANDOFF_V1_FORMAT,
259
+ createdAt: timestamp(input.createdAt, "Agent message handoff.createdAt"),
260
+ expiresAt: timestamp(input.expiresAt, "Agent message handoff.expiresAt"),
261
+ contact: Object.freeze({
262
+ contactId: identifier(input.contact.contactId, "Agent message handoff.contact.contactId"),
263
+ routeCandidateId: routeCandidateId(input.contact.routeCandidateId, "Agent message handoff.contact.routeCandidateId"),
264
+ sourceId: identifier(input.contact.sourceId, "Agent message handoff.contact.sourceId"),
265
+ conversationId: identifier(input.contact.conversationId, "Agent message handoff.contact.conversationId")
266
+ }),
267
+ evidence: Object.freeze({
268
+ corpusRevision: digest(input.evidence.corpusRevision, "Agent message handoff.evidence.corpusRevision"),
269
+ sourceRevision: digest(input.evidence.sourceRevision, "Agent message handoff.evidence.sourceRevision"),
270
+ profileState: profileState(input.evidence.profileState, "Agent message handoff.evidence.profileState"),
271
+ profileEvidenceRevision: input.evidence.profileEvidenceRevision === null ? null : digest(input.evidence.profileEvidenceRevision, "Agent message handoff.evidence.profileEvidenceRevision")
272
+ }),
273
+ wrench: Object.freeze({
274
+ contractId: context.contractId,
275
+ contractHash: context.contractHash,
276
+ routeRef: context.routeRef,
277
+ routeRefSha256: sha256(context.routeRef),
278
+ contextRef: context.contextRef,
279
+ contextRefSha256: sha256(context.contextRef),
280
+ exactDataRevision: context.exactDataRevision,
281
+ latestMessageRevision: context.latestMessageRevision,
282
+ validatedAt: context.validatedAt,
283
+ contextExpiresAt: context.expiresAt
284
+ }),
285
+ turn: Object.freeze({ bubbles: draft.bubbles }),
286
+ privacy: Object.freeze({
287
+ classification: "private-local",
288
+ excludedFields: Object.freeze([
289
+ "attachments",
290
+ "credentials",
291
+ "provider-coordinates",
292
+ "provider-payloads"
293
+ ])
294
+ })
295
+ });
296
+ const lifetime = Date.parse(core.expiresAt) - Date.parse(core.createdAt);
297
+ if (lifetime <= 0 || lifetime > AGENTIC_MESSAGING_V1_LIMITS.handoffLifetimeMilliseconds || core.createdAt < core.wrench.validatedAt) {
298
+ return fail("Agent message handoff timestamps are inconsistent");
299
+ }
300
+ if (core.expiresAt > core.wrench.contextExpiresAt) {
301
+ return fail("Agent message handoff outlives its Wrench context binding");
302
+ }
303
+ const canonicalSha256 = handoffDigest(core);
304
+ return Object.freeze({
305
+ ...core,
306
+ handoffId: `handoff_${canonicalSha256}`,
307
+ integrity: Object.freeze({ algorithm: "sha256", canonicalSha256 })
308
+ });
309
+ }
310
+ function parseAgentMessageHandoffV1(value) {
311
+ const record = object(value, "Agent message handoff");
312
+ exactKeys(record, [
313
+ "schemaVersion",
314
+ "format",
315
+ "handoffId",
316
+ "createdAt",
317
+ "expiresAt",
318
+ "contact",
319
+ "evidence",
320
+ "wrench",
321
+ "turn",
322
+ "privacy",
323
+ "integrity"
324
+ ], "Agent message handoff");
325
+ if (record.schemaVersion !== AGENTIC_MESSAGING_V1_SCHEMA_VERSION || record.format !== AGENT_MESSAGE_HANDOFF_V1_FORMAT)
326
+ return fail("Agent message handoff has the wrong schemaVersion or format");
327
+ const contactRecord = object(record.contact, "Agent message handoff.contact");
328
+ exactKeys(contactRecord, ["contactId", "routeCandidateId", "sourceId", "conversationId"], "Agent message handoff.contact");
329
+ const evidenceRecord = object(record.evidence, "Agent message handoff.evidence");
330
+ exactKeys(evidenceRecord, [
331
+ "corpusRevision",
332
+ "sourceRevision",
333
+ "profileState",
334
+ "profileEvidenceRevision"
335
+ ], "Agent message handoff.evidence");
336
+ const wrenchRecord = object(record.wrench, "Agent message handoff.wrench");
337
+ exactKeys(wrenchRecord, [
338
+ "contractId",
339
+ "contractHash",
340
+ "routeRef",
341
+ "routeRefSha256",
342
+ "contextRef",
343
+ "contextRefSha256",
344
+ "exactDataRevision",
345
+ "latestMessageRevision",
346
+ "validatedAt",
347
+ "contextExpiresAt"
348
+ ], "Agent message handoff.wrench");
349
+ const turnRecord = object(record.turn, "Agent message handoff.turn");
350
+ exactKeys(turnRecord, ["bubbles"], "Agent message handoff.turn");
351
+ const privacyRecord = object(record.privacy, "Agent message handoff.privacy");
352
+ exactKeys(privacyRecord, ["classification", "excludedFields"], "Agent message handoff.privacy");
353
+ const excluded = denseArray(privacyRecord.excludedFields, "Agent message handoff.privacy.excludedFields", 4, 4);
354
+ const expectedExcluded = ["attachments", "credentials", "provider-coordinates", "provider-payloads"];
355
+ if (excluded.some((item, index) => item !== expectedExcluded[index])) {
356
+ return fail("Agent message handoff.privacy.excludedFields is not canonical");
357
+ }
358
+ if (privacyRecord.classification !== "private-local") {
359
+ return fail("Agent message handoff must be classified private-local");
360
+ }
361
+ const integrityRecord = object(record.integrity, "Agent message handoff.integrity");
362
+ exactKeys(integrityRecord, ["algorithm", "canonicalSha256"], "Agent message handoff.integrity");
363
+ if (integrityRecord.algorithm !== "sha256")
364
+ return fail("Agent message handoff integrity algorithm must be sha256");
365
+ const context = parseWrenchMessagingContextBindingV1({
366
+ schemaVersion: AGENTIC_MESSAGING_V1_SCHEMA_VERSION,
367
+ format: WRENCH_MESSAGING_CONTEXT_BINDING_V1_FORMAT,
368
+ contractId: wrenchRecord.contractId,
369
+ contractHash: wrenchRecord.contractHash,
370
+ routeRef: wrenchRecord.routeRef,
371
+ contextRef: wrenchRecord.contextRef,
372
+ exactDataRevision: wrenchRecord.exactDataRevision,
373
+ latestMessageRevision: wrenchRecord.latestMessageRevision,
374
+ validatedAt: wrenchRecord.validatedAt,
375
+ expiresAt: wrenchRecord.contextExpiresAt
376
+ });
377
+ const handoff = createAgentMessageHandoffV1({
378
+ createdAt: timestamp(record.createdAt, "Agent message handoff.createdAt"),
379
+ expiresAt: timestamp(record.expiresAt, "Agent message handoff.expiresAt"),
380
+ contact: {
381
+ contactId: identifier(contactRecord.contactId, "Agent message handoff.contact.contactId"),
382
+ routeCandidateId: routeCandidateId(contactRecord.routeCandidateId, "Agent message handoff.contact.routeCandidateId"),
383
+ sourceId: identifier(contactRecord.sourceId, "Agent message handoff.contact.sourceId"),
384
+ conversationId: identifier(contactRecord.conversationId, "Agent message handoff.contact.conversationId")
385
+ },
386
+ evidence: {
387
+ corpusRevision: digest(evidenceRecord.corpusRevision, "Agent message handoff.evidence.corpusRevision"),
388
+ sourceRevision: digest(evidenceRecord.sourceRevision, "Agent message handoff.evidence.sourceRevision"),
389
+ profileState: profileState(evidenceRecord.profileState, "Agent message handoff.evidence.profileState"),
390
+ profileEvidenceRevision: evidenceRecord.profileEvidenceRevision === null ? null : digest(evidenceRecord.profileEvidenceRevision, "Agent message handoff.evidence.profileEvidenceRevision")
391
+ },
392
+ wrenchContext: context,
393
+ draft: {
394
+ schemaVersion: AGENTIC_MESSAGING_V1_SCHEMA_VERSION,
395
+ format: AGENT_MESSAGE_DRAFT_V1_FORMAT,
396
+ bubbles: bubbles(turnRecord.bubbles, "Agent message handoff.turn.bubbles")
397
+ }
398
+ });
399
+ const suppliedRouteSha = digest(wrenchRecord.routeRefSha256, "Agent message handoff.wrench.routeRefSha256");
400
+ const suppliedContextSha = digest(wrenchRecord.contextRefSha256, "Agent message handoff.wrench.contextRefSha256");
401
+ const suppliedCanonicalSha = digest(integrityRecord.canonicalSha256, "Agent message handoff.integrity.canonicalSha256");
402
+ if (suppliedRouteSha !== handoff.wrench.routeRefSha256 || suppliedContextSha !== handoff.wrench.contextRefSha256 || suppliedCanonicalSha !== handoff.integrity.canonicalSha256 || record.handoffId !== handoff.handoffId)
403
+ return fail("Agent message handoff integrity does not match its canonical content");
404
+ return handoff;
405
+ }
406
+ function wrenchMessagingTurnDigestV1(value) {
407
+ const handoff = parseAgentMessageHandoffV1(value);
408
+ return sha256(canonicalJson({
409
+ schemaVersion: 1,
410
+ format: "wrench.messaging-turn",
411
+ clientIntentSha256: handoff.integrity.canonicalSha256,
412
+ routeRef: handoff.wrench.routeRef,
413
+ contextRef: handoff.wrench.contextRef,
414
+ parts: handoff.turn.bubbles.map((bubble2) => ({
415
+ partId: bubble2.id,
416
+ text: bubble2.text,
417
+ replyRef: bubble2.replyToRef
418
+ }))
419
+ }));
420
+ }
421
+ function parseWrenchMessagingReceiptBindingV1(value) {
422
+ const record = object(value, "Wrench messaging receipt binding");
423
+ exactKeys(record, [
424
+ "schemaVersion",
425
+ "format",
426
+ "contractId",
427
+ "contractHash",
428
+ "clientIntentSha256",
429
+ "routeRefSha256",
430
+ "contextRefSha256",
431
+ "turnDigest",
432
+ "previewDigest",
433
+ "runId",
434
+ "state",
435
+ "partCount",
436
+ "provenPartCount",
437
+ "receiptSha256",
438
+ "recordedAt"
439
+ ], "Wrench messaging receipt binding");
440
+ if (record.schemaVersion !== AGENTIC_MESSAGING_V1_SCHEMA_VERSION || record.format !== WRENCH_MESSAGING_RECEIPT_BINDING_V1_FORMAT)
441
+ return fail("Wrench messaging receipt binding has the wrong schemaVersion or format");
442
+ if (record.contractId !== WRENCH_MESSAGING_RECEIPT_BINDING_V1_CONTRACT_ID || record.contractHash !== WRENCH_MESSAGING_RECEIPT_BINDING_V1_CONTRACT_HASH)
443
+ return fail("Wrench messaging receipt binding has an unsupported contract identity");
444
+ if (record.state !== "submitted" && record.state !== "failed" && record.state !== "partial" && record.state !== "indeterminate")
445
+ return fail("Wrench messaging receipt binding has an invalid state");
446
+ if (!Number.isSafeInteger(record.partCount) || record.partCount < 1 || record.partCount > 8) {
447
+ return fail("Wrench messaging receipt binding.partCount must be from 1 through 8");
448
+ }
449
+ if (!Number.isSafeInteger(record.provenPartCount) || record.provenPartCount < 0 || record.provenPartCount > record.partCount)
450
+ return fail("Wrench messaging receipt binding.provenPartCount is out of range");
451
+ const partCount = record.partCount;
452
+ const provenPartCount = record.provenPartCount;
453
+ if (record.state === "submitted" && provenPartCount !== partCount || record.state === "failed" && provenPartCount !== 0 || record.state === "partial" && (provenPartCount < 1 || provenPartCount >= partCount) || record.state === "indeterminate" && provenPartCount >= partCount)
454
+ return fail("Wrench messaging receipt binding state does not match its proven prefix");
455
+ const parsed = Object.freeze({
456
+ schemaVersion: AGENTIC_MESSAGING_V1_SCHEMA_VERSION,
457
+ format: WRENCH_MESSAGING_RECEIPT_BINDING_V1_FORMAT,
458
+ contractId: WRENCH_MESSAGING_RECEIPT_BINDING_V1_CONTRACT_ID,
459
+ contractHash: WRENCH_MESSAGING_RECEIPT_BINDING_V1_CONTRACT_HASH,
460
+ clientIntentSha256: digest(record.clientIntentSha256, "Wrench messaging receipt binding.clientIntentSha256"),
461
+ routeRefSha256: digest(record.routeRefSha256, "Wrench messaging receipt binding.routeRefSha256"),
462
+ contextRefSha256: digest(record.contextRefSha256, "Wrench messaging receipt binding.contextRefSha256"),
463
+ turnDigest: digest(record.turnDigest, "Wrench messaging receipt binding.turnDigest"),
464
+ previewDigest: digest(record.previewDigest, "Wrench messaging receipt binding.previewDigest"),
465
+ runId: identifier(record.runId, "Wrench messaging receipt binding.runId", 256),
466
+ state: record.state,
467
+ partCount,
468
+ provenPartCount,
469
+ receiptSha256: digest(record.receiptSha256, "Wrench messaging receipt binding.receiptSha256"),
470
+ recordedAt: timestamp(record.recordedAt, "Wrench messaging receipt binding.recordedAt")
471
+ });
472
+ const { receiptSha256, ...receiptCore } = parsed;
473
+ if (sha256(canonicalJson(receiptCore)) !== receiptSha256) {
474
+ return fail("Wrench messaging receipt binding receiptSha256 does not match its canonical content");
475
+ }
476
+ return parsed;
477
+ }
478
+ function agentMessageRouteCandidateId(sourceId, conversationId) {
479
+ return `route_${sha256(canonicalJson({
480
+ conversationId: identifier(conversationId, "Source-conversation route.conversationId"),
481
+ sourceId: identifier(sourceId, "Source-conversation route.sourceId")
482
+ }))}`;
483
+ }
28
484
  export {
29
485
  wrenchMessagingTurnDigestV1,
30
486
  parseWrenchMessagingReceiptBindingV1,