@ouro.bot/cli 0.1.0-alpha.725 → 0.1.0-alpha.727

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.
@@ -0,0 +1,311 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.reserveObservation = reserveObservation;
4
+ exports.beginObservationBatch = beginObservationBatch;
5
+ exports.reserveObservationFromBatch = reserveObservationFromBatch;
6
+ exports.clearPending = clearPending;
7
+ exports.reactivateObservation = reactivateObservation;
8
+ exports.mergeObservationReservations = mergeObservationReservations;
9
+ exports.promote = promote;
10
+ exports.isCurrent = isCurrent;
11
+ exports.observationSchedulingKeys = observationSchedulingKeys;
12
+ exports.awaitDeliveryAdmission = awaitDeliveryAdmission;
13
+ exports.isDeliveryAdmittedNow = isDeliveryAdmittedNow;
14
+ exports.cancel = cancel;
15
+ exports.finish = finish;
16
+ exports.__resetBlueBubblesLatestTurnsForTests = __resetBlueBubblesLatestTurnsForTests;
17
+ const node_crypto_1 = require("node:crypto");
18
+ const runtime_1 = require("../../nerves/runtime");
19
+ const AMBIGUOUS_IDENTIFIER = Symbol("ambiguous-bluebubbles-identifier");
20
+ const UNRESOLVED_SCHEDULING_KEY = "unresolved:*";
21
+ const pending = new Map();
22
+ const lanes = new Map();
23
+ const identifierBindings = new Map();
24
+ const reservationHints = new WeakMap();
25
+ const batchReservations = new WeakMap();
26
+ const observationEpoch = `${new Date().toISOString()}/${(0, node_crypto_1.randomUUID)()}`;
27
+ let nextOrdinal = 0;
28
+ function clean(value) {
29
+ const trimmed = value?.trim();
30
+ return trimmed && trimmed.toLowerCase() !== "unknown" ? trimmed : null;
31
+ }
32
+ function canonicalIdentifier(value) {
33
+ const trimmed = clean(value);
34
+ if (!trimmed)
35
+ return null;
36
+ if (trimmed.includes("@"))
37
+ return trimmed.toLowerCase();
38
+ if (/^[+\d\s().-]+$/.test(trimmed)) {
39
+ const compact = trimmed.replace(/[^\d+]/g, "");
40
+ if (compact)
41
+ return compact;
42
+ }
43
+ return trimmed;
44
+ }
45
+ function guidAlias(guid) {
46
+ return `guid:${guid}`;
47
+ }
48
+ function identifierAlias(identifier) {
49
+ return `identifier:${identifier}`;
50
+ }
51
+ function observationHints(input) {
52
+ const guid = clean(input.chatGuid);
53
+ const identifier = canonicalIdentifier(input.chatIdentifier);
54
+ return [
55
+ ...(guid ? [guidAlias(guid)] : []),
56
+ ...(identifier ? [identifierAlias(identifier)] : []),
57
+ ];
58
+ }
59
+ function settlePending(ordinal) {
60
+ const entry = pending.get(ordinal);
61
+ if (!entry)
62
+ return;
63
+ pending.delete(ordinal);
64
+ entry.settle();
65
+ }
66
+ function createReservation(ordinal, input) {
67
+ const knownHints = new Set(observationHints(input));
68
+ const reservation = Object.freeze({
69
+ observationEpoch,
70
+ ordinal,
71
+ get hints() {
72
+ return Object.freeze([...knownHints]);
73
+ },
74
+ });
75
+ reservationHints.set(reservation, knownHints);
76
+ let settle;
77
+ const settled = new Promise((resolve) => {
78
+ settle = resolve;
79
+ });
80
+ pending.set(ordinal, {
81
+ reservation,
82
+ hints: new Set(knownHints),
83
+ settled,
84
+ settle,
85
+ });
86
+ return reservation;
87
+ }
88
+ function reserveObservation(input) {
89
+ return createReservation(++nextOrdinal, input);
90
+ }
91
+ function beginObservationBatch(size) {
92
+ if (!Number.isSafeInteger(size) || size <= 0)
93
+ throw new Error("bluebubbles_observation_batch_size_invalid");
94
+ // BlueBubbles' `before` filter is inclusive. Subtract one millisecond so a
95
+ // row created in the same clock tick after this reservation cannot be
96
+ // backdated into the already-reserved ordinal range.
97
+ const batch = Object.freeze({
98
+ highOrdinal: nextOrdinal + size,
99
+ size,
100
+ beforeTimestamp: Date.now() - 1,
101
+ });
102
+ nextOrdinal = batch.highOrdinal;
103
+ batchReservations.set(batch, new Set());
104
+ return batch;
105
+ }
106
+ function reserveObservationFromBatch(batch, newestOffset, input) {
107
+ const used = batchReservations.get(batch);
108
+ if (!used || !Number.isSafeInteger(newestOffset) || newestOffset < 0 || newestOffset >= batch.size) {
109
+ throw new Error("bluebubbles_observation_batch_offset_invalid");
110
+ }
111
+ const ordinal = batch.highOrdinal - newestOffset;
112
+ if (used.has(ordinal))
113
+ throw new Error("bluebubbles_observation_batch_offset_reused");
114
+ used.add(ordinal);
115
+ return createReservation(ordinal, input);
116
+ }
117
+ function clearPending(reservation) {
118
+ settlePending(reservation.ordinal);
119
+ }
120
+ function reactivateObservation(reservation) {
121
+ if (pending.has(reservation.ordinal))
122
+ return;
123
+ const knownHints = reservationHints.get(reservation);
124
+ if (!knownHints)
125
+ throw new Error("bluebubbles_observation_reservation_unknown");
126
+ let settle;
127
+ const settled = new Promise((resolve) => {
128
+ settle = resolve;
129
+ });
130
+ pending.set(reservation.ordinal, {
131
+ reservation,
132
+ hints: new Set(knownHints),
133
+ settled,
134
+ settle,
135
+ });
136
+ }
137
+ function mergeObservationReservations(primary, duplicate) {
138
+ if (primary === duplicate)
139
+ return primary;
140
+ const primaryHints = reservationHints.get(primary);
141
+ const duplicateHints = reservationHints.get(duplicate);
142
+ if (!primaryHints || !duplicateHints)
143
+ throw new Error("bluebubbles_observation_reservation_unknown");
144
+ for (const hint of duplicateHints)
145
+ primaryHints.add(hint);
146
+ const primaryPending = pending.get(primary.ordinal);
147
+ if (primaryPending) {
148
+ for (const hint of duplicateHints)
149
+ primaryPending.hints.add(hint);
150
+ }
151
+ settlePending(duplicate.ordinal);
152
+ return primary;
153
+ }
154
+ function bindIdentifier(identifier, guid) {
155
+ const existing = identifierBindings.get(identifier);
156
+ if (existing === undefined) {
157
+ identifierBindings.set(identifier, guid);
158
+ return true;
159
+ }
160
+ if (existing === guid)
161
+ return true;
162
+ identifierBindings.set(identifier, AMBIGUOUS_IDENTIFIER);
163
+ return false;
164
+ }
165
+ function resolveCanonicalChat(input) {
166
+ const inputGuid = clean(input.chatGuid);
167
+ const observedIdentifier = clean(input.chatIdentifier);
168
+ const identifier = canonicalIdentifier(input.chatIdentifier);
169
+ let guid = inputGuid;
170
+ if (guid && identifier)
171
+ bindIdentifier(identifier, guid);
172
+ if (!guid && identifier) {
173
+ const binding = identifierBindings.get(identifier);
174
+ if (typeof binding === "string")
175
+ guid = binding;
176
+ }
177
+ if (!guid)
178
+ return null;
179
+ return {
180
+ chatGuid: guid,
181
+ ...(observedIdentifier && identifier && identifierBindings.get(identifier) === guid
182
+ ? { chatIdentifier: observedIdentifier }
183
+ : {}),
184
+ sessionKey: `chat:${guid}`,
185
+ };
186
+ }
187
+ function promote(reservation, input, options = {}) {
188
+ const canonicalChat = resolveCanonicalChat(input);
189
+ settlePending(reservation.ordinal);
190
+ if (!canonicalChat)
191
+ return { status: "unresolved" };
192
+ const chatKey = guidAlias(canonicalChat.chatGuid);
193
+ const lane = lanes.get(chatKey) ?? { highWater: 0, current: null };
194
+ const sameGenerationRetry = reservation.ordinal === lane.highWater
195
+ && options.allowSameGenerationRetry === true
196
+ && lane.current === null;
197
+ if (reservation.ordinal < lane.highWater || (reservation.ordinal === lane.highWater && !sameGenerationRetry)) {
198
+ return { status: "stale" };
199
+ }
200
+ lane.current?.controller.abort(new Error("superseded"));
201
+ const controller = new AbortController();
202
+ const capability = Object.freeze({
203
+ ordinal: reservation.ordinal,
204
+ chatKey,
205
+ canonicalChat: Object.freeze(canonicalChat),
206
+ signal: controller.signal,
207
+ controller,
208
+ });
209
+ lane.highWater = reservation.ordinal;
210
+ lane.current = capability;
211
+ lanes.set(chatKey, lane);
212
+ (0, runtime_1.emitNervesEvent)({
213
+ component: "senses",
214
+ event: "senses.bluebubbles_latest_turn_promoted",
215
+ message: "promoted the latest process-local bluebubbles turn",
216
+ meta: {
217
+ chatKey,
218
+ ordinal: reservation.ordinal,
219
+ aliasCount: 1 + [...identifierBindings.values()].filter((binding) => binding === canonicalChat.chatGuid).length,
220
+ },
221
+ });
222
+ return { status: "promoted", capability };
223
+ }
224
+ function internal(capability) {
225
+ return capability;
226
+ }
227
+ function isCurrent(capability) {
228
+ const lane = lanes.get(capability.chatKey);
229
+ return lane?.current === capability && !capability.signal.aborted;
230
+ }
231
+ function pendingIntersects(capability, entry) {
232
+ if (entry.reservation.ordinal <= capability.ordinal)
233
+ return false;
234
+ const hasGuidHint = [...entry.hints].some((hint) => hint.startsWith("guid:"));
235
+ if (entry.hints.size === 0)
236
+ return true;
237
+ for (const hint of entry.hints) {
238
+ if (hint === capability.chatKey)
239
+ return true;
240
+ if (hint.startsWith("identifier:")) {
241
+ const identifier = hint.slice("identifier:".length);
242
+ const binding = identifierBindings.get(identifier);
243
+ if (binding === capability.canonicalChat.chatGuid)
244
+ return true;
245
+ // An identifier-only observation can repair into any GUID lane. Until
246
+ // that relation is proved, it is a temporary global delivery fence—not
247
+ // an active shared chat lane. A direct GUID hint keeps paired events
248
+ // independently schedulable through that exact coordinate.
249
+ if (!hasGuidHint && typeof binding !== "string")
250
+ return true;
251
+ }
252
+ }
253
+ return false;
254
+ }
255
+ function observationSchedulingKeys(reservation) {
256
+ const hints = reservationHints.get(reservation);
257
+ if (!hints)
258
+ throw new Error("bluebubbles_observation_reservation_unknown");
259
+ const keys = new Set();
260
+ const hasGuidHint = [...hints].some((hint) => hint.startsWith("guid:"));
261
+ if (hints.size === 0)
262
+ keys.add(UNRESOLVED_SCHEDULING_KEY);
263
+ for (const hint of hints) {
264
+ if (hint.startsWith("identifier:")) {
265
+ const identifier = hint.slice("identifier:".length);
266
+ const binding = identifierBindings.get(identifier);
267
+ keys.add(typeof binding === "string"
268
+ ? guidAlias(binding)
269
+ : hasGuidHint ? hint : UNRESOLVED_SCHEDULING_KEY);
270
+ }
271
+ else {
272
+ keys.add(hint);
273
+ }
274
+ }
275
+ return Object.freeze([...keys]);
276
+ }
277
+ async function awaitDeliveryAdmission(capability) {
278
+ const value = internal(capability);
279
+ while (isCurrent(value)) {
280
+ const blockers = [...pending.values()].filter((entry) => pendingIntersects(value, entry));
281
+ if (blockers.length === 0)
282
+ return true;
283
+ await Promise.all(blockers.map((entry) => entry.settled));
284
+ }
285
+ return false;
286
+ }
287
+ function isDeliveryAdmittedNow(capability) {
288
+ const value = internal(capability);
289
+ return isCurrent(value)
290
+ && ![...pending.values()].some((entry) => pendingIntersects(value, entry));
291
+ }
292
+ function cancel(capability, reason = "cancelled") {
293
+ const lane = lanes.get(capability.chatKey);
294
+ if (lane?.current !== capability)
295
+ return;
296
+ internal(capability).controller.abort(new Error(reason));
297
+ lane.current = null;
298
+ }
299
+ function finish(capability) {
300
+ const lane = lanes.get(capability.chatKey);
301
+ if (lane?.current === capability)
302
+ lane.current = null;
303
+ }
304
+ function __resetBlueBubblesLatestTurnsForTests() {
305
+ for (const entry of pending.values())
306
+ entry.settle();
307
+ pending.clear();
308
+ lanes.clear();
309
+ identifierBindings.clear();
310
+ nextOrdinal = 0;
311
+ }
@@ -38,6 +38,7 @@ exports.blueBubblesOutboundRecordPath = blueBubblesOutboundRecordPath;
38
38
  exports.buildBlueBubblesRouteProof = buildBlueBubblesRouteProof;
39
39
  exports.buildBlueBubblesAttachmentProof = buildBlueBubblesAttachmentProof;
40
40
  exports.readBlueBubblesOutboundRecord = readBlueBubblesOutboundRecord;
41
+ exports.readBlueBubblesOutboundRecordByIdempotencyKey = readBlueBubblesOutboundRecordByIdempotencyKey;
41
42
  exports.reserveBlueBubblesOutbound = reserveBlueBubblesOutbound;
42
43
  exports.markBlueBubblesOutboundAccepted = markBlueBubblesOutboundAccepted;
43
44
  exports.markBlueBubblesOutboundEnqueued = markBlueBubblesOutboundEnqueued;
@@ -174,6 +175,9 @@ function readBlueBubblesOutboundRecord(agentRoot, recordId) {
174
175
  return null;
175
176
  }
176
177
  }
178
+ function readBlueBubblesOutboundRecordByIdempotencyKey(agentRoot, idempotencyKey) {
179
+ return readBlueBubblesOutboundRecord(agentRoot, recordIdFor(idempotencyKey.trim()));
180
+ }
177
181
  function writeBlueBubblesOutboundRecord(agentRoot, record) {
178
182
  writeJsonAtomic(blueBubblesOutboundRecordPath(agentRoot, record.recordId), record);
179
183
  return record;
@@ -25,17 +25,11 @@ function classifyBlueBubblesReaction(input) {
25
25
  else if (input.canonicalValue === "unknown") {
26
26
  decision = { route: "capture_only", outcome: "capture_only_unknown" };
27
27
  }
28
- else if (input.targetAuthorship !== "agent") {
29
- decision = { route: "capture_only", outcome: "capture_only_target_not_agent" };
30
- }
31
- else if (input.trustedActor === undefined) {
32
- decision = { route: "trust_required" };
33
- }
34
- else if (!input.trustedActor) {
35
- decision = { route: "capture_only", outcome: "capture_only_untrusted_actor" };
28
+ else if (input.canonicalValue === "question") {
29
+ decision = { route: "capture_only", outcome: "capture_only_question" };
36
30
  }
37
31
  else {
38
- decision = { route: "restricted_feedback" };
32
+ decision = { route: "capture_only", outcome: "capture_only_negative" };
39
33
  }
40
34
  (0, runtime_1.emitNervesEvent)({
41
35
  component: "senses",
@@ -45,10 +39,8 @@ function classifyBlueBubblesReaction(input) {
45
39
  action: input.action,
46
40
  canonicalValue: input.canonicalValue,
47
41
  fromMe: input.fromMe,
48
- targetAuthorship: input.targetAuthorship,
49
- trustEvaluated: input.trustedActor !== undefined,
50
42
  route: decision.route,
51
- outcome: decision.route === "capture_only" ? decision.outcome : null,
43
+ outcome: decision.outcome,
52
44
  },
53
45
  });
54
46
  return decision;
@@ -55,6 +55,7 @@ exports.writeBlueBubblesSemanticCapture = writeBlueBubblesSemanticCapture;
55
55
  exports.readBlueBubblesSemanticCapture = readBlueBubblesSemanticCapture;
56
56
  exports.readBlueBubblesSemanticCaptureAtRoot = readBlueBubblesSemanticCaptureAtRoot;
57
57
  exports.listPendingBlueBubblesSemanticCaptures = listPendingBlueBubblesSemanticCaptures;
58
+ exports.compareBlueBubblesSemanticCaptureOrder = compareBlueBubblesSemanticCaptureOrder;
58
59
  exports.writeBlueBubblesSemanticHandled = writeBlueBubblesSemanticHandled;
59
60
  exports.readBlueBubblesSemanticHandled = readBlueBubblesSemanticHandled;
60
61
  exports.acquireBlueBubblesSemanticClaim = acquireBlueBubblesSemanticClaim;
@@ -92,6 +93,14 @@ const SEMANTIC_OWNERSHIP_SCHEMA_SQL = `CREATE TABLE owner_leases (
92
93
  owner_json TEXT NOT NULL
93
94
  ) STRICT, WITHOUT ROWID`;
94
95
  const CAPTURE_KEYS = ["schemaVersion", "canonicalKey", "keyHash", "providerNamespace", "capturedAt", "event"];
96
+ const OBSERVATION_ORDER_KEYS = [
97
+ "schemaVersion",
98
+ "keyHash",
99
+ "captureHash",
100
+ "observedAt",
101
+ "observationEpoch",
102
+ "observationOrdinal",
103
+ ];
95
104
  const CAPTURE_EVENT_KEYS = [
96
105
  "provider",
97
106
  "kind",
@@ -157,6 +166,7 @@ function getBlueBubblesSemanticPathsAtRoot(agentRoot) {
157
166
  root,
158
167
  cutover: path.join(root, "cutover.json"),
159
168
  captures: path.join(root, "captures"),
169
+ observationOrders: path.join(root, "observation-orders"),
160
170
  handled: path.join(root, "handled"),
161
171
  claims: path.join(root, "claims"),
162
172
  coordinates: path.join(root, "coordinates"),
@@ -413,6 +423,22 @@ function buildBlueBubblesSemanticCapture(input) {
413
423
  : null;
414
424
  if (!providerNamespace || !capturedAt || !actorExternalId)
415
425
  return null;
426
+ const inputObservationEpoch = input.observationEpoch;
427
+ const inputObservationOrdinal = input.observationOrdinal;
428
+ const observationClock = inputObservationEpoch === undefined
429
+ && inputObservationOrdinal === undefined
430
+ ? null
431
+ : isBlueBubblesObservationEpoch(inputObservationEpoch)
432
+ && typeof inputObservationOrdinal === "number"
433
+ && Number.isSafeInteger(inputObservationOrdinal)
434
+ && inputObservationOrdinal > 0
435
+ ? {
436
+ observationEpoch: inputObservationEpoch,
437
+ observationOrdinal: inputObservationOrdinal,
438
+ }
439
+ : false;
440
+ if (observationClock === false)
441
+ return null;
416
442
  const kind = input.event.kind === "message" ? "message" : input.event.mutationType;
417
443
  const eventGuid = requiredIdentifier(input.event.messageGuid);
418
444
  const targetGuid = input.event.kind === "mutation"
@@ -451,7 +477,7 @@ function buildBlueBubblesSemanticCapture(input) {
451
477
  externalId,
452
478
  displayName: null,
453
479
  })));
454
- return {
480
+ const capture = {
455
481
  schemaVersion: 1,
456
482
  canonicalKey: identity.canonicalKey,
457
483
  keyHash: identity.keyHash,
@@ -484,6 +510,16 @@ function buildBlueBubblesSemanticCapture(input) {
484
510
  contentSha256: kind === "edit" ? textSha256 : null,
485
511
  },
486
512
  };
513
+ if (observationClock) {
514
+ attachBlueBubblesSemanticObservationOrder(capture, {
515
+ schemaVersion: 1,
516
+ keyHash: capture.keyHash,
517
+ captureHash: blueBubblesSemanticCaptureHash(capture),
518
+ observedAt: capturedAt,
519
+ ...observationClock,
520
+ });
521
+ }
522
+ return capture;
487
523
  }
488
524
  function buildBlueBubblesReactionCoordinateRecord(input) {
489
525
  return {
@@ -551,12 +587,17 @@ function writeBlueBubblesSemanticCapture(agentName, capture, deps = {}) {
551
587
  try {
552
588
  const paths = getBlueBubblesSemanticPaths(agentName);
553
589
  const finalPath = semanticRecordPath(paths.captures, capture.keyHash);
590
+ const validCapture = isBlueBubblesSemanticCapture(capture, capture.keyHash);
554
591
  const existing = readExistingCaptureForWrite(agentName, capture.keyHash, deps);
555
- if (existing)
592
+ if (existing) {
593
+ if (validCapture && capturesAreEquivalent(existing, capture)) {
594
+ persistBlueBubblesSemanticObservationOrder(agentName, capture, deps);
595
+ }
556
596
  return finishCaptureComparison(agentName, existing, capture, deps);
557
- if (!isBlueBubblesSemanticCapture(capture, capture.keyHash)) {
558
- throw semanticStoreError("semantic_capture_invalid");
559
597
  }
598
+ if (!validCapture)
599
+ throw semanticStoreError("semantic_capture_invalid");
600
+ persistBlueBubblesSemanticObservationOrder(agentName, capture, deps);
560
601
  const publication = publishImmutableSemanticRecord(paths.captures, finalPath, capture, deps);
561
602
  if (publication === "exists") {
562
603
  const winner = readExistingCaptureForWrite(agentName, capture.keyHash, deps);
@@ -581,12 +622,14 @@ function writeBlueBubblesSemanticCapture(agentName, capture, deps = {}) {
581
622
  function readBlueBubblesSemanticCapture(agentName, keyHash, deps = {}) {
582
623
  const paths = getBlueBubblesSemanticPaths(agentName);
583
624
  const finalPath = semanticRecordPath(paths.captures, keyHash);
584
- return readSemanticRecord(finalPath, "capture", keyHash, (value) => isBlueBubblesSemanticCapture(value, keyHash), deps);
625
+ const capture = readSemanticRecord(finalPath, "capture", keyHash, (value) => isBlueBubblesSemanticCapture(value, keyHash), deps);
626
+ return capture ? attachStoredBlueBubblesSemanticObservationOrder(paths, capture, deps) : null;
585
627
  }
586
628
  function readBlueBubblesSemanticCaptureAtRoot(agentRoot, keyHash, deps = {}) {
587
629
  const paths = getBlueBubblesSemanticPathsAtRoot(agentRoot);
588
630
  const finalPath = semanticRecordPath(paths.captures, keyHash);
589
- return readSemanticRecord(finalPath, "capture", keyHash, (value) => isBlueBubblesSemanticCapture(value, keyHash), deps);
631
+ const capture = readSemanticRecord(finalPath, "capture", keyHash, (value) => isBlueBubblesSemanticCapture(value, keyHash), deps);
632
+ return capture ? attachStoredBlueBubblesSemanticObservationOrder(paths, capture, deps) : null;
590
633
  }
591
634
  function listPendingBlueBubblesSemanticCaptures(agentName, deps = {}) {
592
635
  const storeFs = semanticFs(deps);
@@ -606,7 +649,109 @@ function listPendingBlueBubblesSemanticCaptures(agentName, deps = {}) {
606
649
  continue;
607
650
  captures.push(capture);
608
651
  }
609
- return captures.sort((left, right) => (left.capturedAt.localeCompare(right.capturedAt) || left.keyHash.localeCompare(right.keyHash)));
652
+ return captures.sort((left, right) => (compareBlueBubblesSemanticCaptureOrder(left, right)));
653
+ }
654
+ function compareBlueBubblesSemanticCaptureOrder(left, right) {
655
+ if (left.observationEpoch !== undefined
656
+ && left.observationEpoch === right.observationEpoch
657
+ && left.observationOrdinal !== undefined
658
+ && right.observationOrdinal !== undefined
659
+ && left.observationOrdinal !== right.observationOrdinal) {
660
+ return left.observationOrdinal - right.observationOrdinal;
661
+ }
662
+ const capturedOrder = (left.observationObservedAt ?? left.capturedAt)
663
+ .localeCompare(right.observationObservedAt ?? right.capturedAt);
664
+ if (capturedOrder !== 0)
665
+ return capturedOrder;
666
+ if (left.observationEpoch === undefined && right.observationEpoch === undefined)
667
+ return 0;
668
+ const epochOrder = (left.observationEpoch ?? "").localeCompare(right.observationEpoch ?? "");
669
+ if (epochOrder !== 0)
670
+ return epochOrder;
671
+ return left.keyHash.localeCompare(right.keyHash);
672
+ }
673
+ function attachBlueBubblesSemanticObservationOrder(capture, order) {
674
+ Object.defineProperties(capture, {
675
+ observationObservedAt: {
676
+ configurable: true,
677
+ enumerable: false,
678
+ value: order.observedAt,
679
+ },
680
+ observationEpoch: {
681
+ configurable: true,
682
+ enumerable: false,
683
+ value: order.observationEpoch,
684
+ },
685
+ observationOrdinal: {
686
+ configurable: true,
687
+ enumerable: false,
688
+ value: order.observationOrdinal,
689
+ },
690
+ });
691
+ return capture;
692
+ }
693
+ function blueBubblesSemanticObservationOrderFromCapture(capture) {
694
+ if (!isBlueBubblesObservationEpoch(capture.observationEpoch)
695
+ || typeof capture.observationOrdinal !== "number"
696
+ || !Number.isSafeInteger(capture.observationOrdinal)
697
+ || capture.observationOrdinal <= 0)
698
+ return null;
699
+ return {
700
+ schemaVersion: 1,
701
+ keyHash: capture.keyHash,
702
+ captureHash: blueBubblesSemanticCaptureHash(capture),
703
+ observedAt: capture.capturedAt,
704
+ observationEpoch: capture.observationEpoch,
705
+ observationOrdinal: capture.observationOrdinal,
706
+ };
707
+ }
708
+ function compareBlueBubblesSemanticObservationOrders(left, right) {
709
+ if (left.observationEpoch === right.observationEpoch
710
+ && left.observationOrdinal !== right.observationOrdinal) {
711
+ return left.observationOrdinal - right.observationOrdinal;
712
+ }
713
+ const observedOrder = left.observedAt.localeCompare(right.observedAt);
714
+ if (observedOrder !== 0)
715
+ return observedOrder;
716
+ const epochOrder = left.observationEpoch.localeCompare(right.observationEpoch);
717
+ if (epochOrder !== 0)
718
+ return epochOrder;
719
+ return left.observationOrdinal - right.observationOrdinal;
720
+ }
721
+ function persistBlueBubblesSemanticObservationOrder(agentName, capture, deps) {
722
+ const candidate = blueBubblesSemanticObservationOrderFromCapture(capture);
723
+ if (!candidate)
724
+ return;
725
+ const paths = getBlueBubblesSemanticPaths(agentName);
726
+ const finalPath = semanticRecordPath(paths.observationOrders, candidate.captureHash);
727
+ const startedAt = semanticNow(deps).getTime();
728
+ while (true) {
729
+ const result = withExclusiveSemanticOwnership(paths.ownership, deps, () => {
730
+ const inspected = inspectSemanticRecord(finalPath, capture.keyHash, (value) => (isBlueBubblesSemanticObservationOrderRecord(value, capture.keyHash, candidate.captureHash)), deps);
731
+ if (inspected.status === "valid") {
732
+ if (compareBlueBubblesSemanticObservationOrders(inspected.value, candidate) <= 0) {
733
+ fsyncSemanticDirectory(paths.observationOrders, semanticFs(deps));
734
+ return;
735
+ }
736
+ }
737
+ else if (inspected.status === "invalid") {
738
+ quarantineSemanticRecord(finalPath, "observation-order", capture.keyHash, deps, inspected.reason);
739
+ }
740
+ writeMutableSemanticRecord(paths.observationOrders, finalPath, candidate, deps);
741
+ });
742
+ if (result.status === "completed")
743
+ return;
744
+ if (semanticNow(deps).getTime() - startedAt >= SEMANTIC_CLAIM_TIMEOUT_MS) {
745
+ throw semanticStoreError("semantic_ownership_busy");
746
+ }
747
+ semanticSleepSync(SEMANTIC_CLAIM_POLL_MS, deps);
748
+ }
749
+ }
750
+ function attachStoredBlueBubblesSemanticObservationOrder(paths, capture, deps) {
751
+ const captureHash = blueBubblesSemanticCaptureHash(capture);
752
+ const finalPath = semanticRecordPath(paths.observationOrders, captureHash);
753
+ const order = readSemanticRecord(finalPath, "observation-order", capture.keyHash, (value) => (isBlueBubblesSemanticObservationOrderRecord(value, capture.keyHash, captureHash)), deps);
754
+ return order ? attachBlueBubblesSemanticObservationOrder(capture, order) : capture;
610
755
  }
611
756
  function writeBlueBubblesSemanticHandled(agentName, record, deps = {}) {
612
757
  try {
@@ -822,7 +967,10 @@ function finishHandledComparison(agentName, existing, candidate, deps) {
822
967
  return "semantic_handled_collision";
823
968
  }
824
969
  function capturesAreEquivalent(left, right) {
825
- const project = (capture) => ({
970
+ return blueBubblesSemanticCaptureHash(left) === blueBubblesSemanticCaptureHash(right);
971
+ }
972
+ function blueBubblesSemanticCaptureHash(capture) {
973
+ const projection = {
826
974
  schemaVersion: capture.schemaVersion,
827
975
  canonicalKey: capture.canonicalKey,
828
976
  keyHash: capture.keyHash,
@@ -844,8 +992,8 @@ function capturesAreEquivalent(left, right) {
844
992
  revision: capture.event.revision,
845
993
  contentSha256: capture.event.contentSha256,
846
994
  },
847
- });
848
- return JSON.stringify(project(left)) === JSON.stringify(project(right));
995
+ };
996
+ return sha256Utf8(JSON.stringify(projection));
849
997
  }
850
998
  function handledRecordsAreEquivalent(left, right) {
851
999
  const project = (record) => ({
@@ -1379,6 +1527,26 @@ function isBlueBubblesSemanticCapture(value, expectedKeyHash) {
1379
1527
  }
1380
1528
  return captureIdentityMatchesEvent(value);
1381
1529
  }
1530
+ function isBlueBubblesSemanticObservationOrderRecord(value, expectedKeyHash, expectedCaptureHash) {
1531
+ return isRecord(value)
1532
+ && hasExactKeys(value, OBSERVATION_ORDER_KEYS)
1533
+ && value.schemaVersion === 1
1534
+ && value.keyHash === expectedKeyHash
1535
+ && value.captureHash === expectedCaptureHash
1536
+ && exactIsoMilliseconds(value.observedAt) !== null
1537
+ && isBlueBubblesObservationEpoch(value.observationEpoch)
1538
+ && typeof value.observationOrdinal === "number"
1539
+ && Number.isSafeInteger(value.observationOrdinal)
1540
+ && value.observationOrdinal > 0;
1541
+ }
1542
+ function isBlueBubblesObservationEpoch(value) {
1543
+ if (typeof value !== "string")
1544
+ return false;
1545
+ const parts = value.split("/");
1546
+ return parts.length === 2
1547
+ && exactIsoMilliseconds(parts[0]) !== null
1548
+ && UUID_V4_PATTERN.test(parts[1]);
1549
+ }
1382
1550
  function captureIdentityMatchesEvent(value) {
1383
1551
  const event = value.event;
1384
1552
  const coordinateGeneration = event.kind === "reaction"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.725",
3
+ "version": "0.1.0-alpha.727",
4
4
  "engines": {
5
5
  "node": ">=22"
6
6
  },