@ouro.bot/cli 0.1.0-alpha.473 → 0.1.0-alpha.475

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.json CHANGED
@@ -1,6 +1,22 @@
1
1
  {
2
2
  "_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
3
3
  "versions": [
4
+ {
5
+ "version": "0.1.0-alpha.475",
6
+ "changes": [
7
+ "Native mail autonomy now counts recently submitted provider-backed sends against the rate window immediately, so Azure Communication Services deliveries cannot slip past the autonomous send limit while delivery events are still pending.",
8
+ "Mail autonomy coverage now locks the submitted-provider case explicitly, preventing back-to-back autonomous sends from bypassing policy before provider reconciliation arrives.",
9
+ "The `ouro.bot` wrapper stays version-synced for the mail autonomy rate-limit repair release."
10
+ ]
11
+ },
12
+ {
13
+ "version": "0.1.0-alpha.474",
14
+ "changes": [
15
+ "DNS workflow planning now handles duplicate-capable records safely by matching exact type/name/content identity before singleton replacement, so adding an ACS apex TXT verification record cannot rewrite unrelated Google, Microsoft, or SPF TXT records.",
16
+ "DNS rollback planning can now delete only the extra allowlisted duplicate record that was not present in the backup while preserving sibling TXT records with the same name.",
17
+ "`@ouro.bot/cli` and the `ouro.bot` wrapper are version-synced for the DNS duplicate-record safety release."
18
+ ]
19
+ },
4
20
  {
5
21
  "version": "0.1.0-alpha.473",
6
22
  "changes": [
@@ -266,28 +266,57 @@ function recordsEqual(left, right) {
266
266
  left.ttl === right.ttl &&
267
267
  priorityEqual;
268
268
  }
269
+ function recordIdentityKey(record) {
270
+ const priority = record.type === "MX" ? String(record.priority ?? 0) : "";
271
+ return `${recordKey(record)}:${record.content}:${priority}`;
272
+ }
273
+ function sameRecordIdentity(left, right) {
274
+ return recordIdentityKey(left) === recordIdentityKey(right);
275
+ }
276
+ function recordsWithKey(records, key) {
277
+ return records.filter((record) => recordKey(record) === key);
278
+ }
279
+ function findCurrentRecordForDesired(input) {
280
+ const key = recordKey(input.desired);
281
+ const currentSameKey = recordsWithKey(input.currentRecords, key);
282
+ const exact = currentSameKey.find((record) => sameRecordIdentity(record, input.desired));
283
+ if (exact)
284
+ return exact;
285
+ const desiredSameKey = recordsWithKey(input.desiredRecords, key);
286
+ if (currentSameKey.length === 1 && desiredSameKey.length === 1)
287
+ return currentSameKey[0];
288
+ return undefined;
289
+ }
269
290
  function planDnsWorkflow(input) {
270
291
  assertDesiredRecordsAllowed(input.binding);
271
292
  const allowedKeys = new Set(input.binding.resources.records.map(recordKey));
272
- const desiredKeys = new Set(input.binding.desired.records.map(recordKey));
273
293
  const changes = [];
294
+ const matchedCurrentRecords = new Set();
274
295
  for (const desired of input.binding.desired.records) {
275
- const current = input.currentRecords.find((record) => recordKey(record) === recordKey(desired));
296
+ const current = findCurrentRecordForDesired({
297
+ desired,
298
+ desiredRecords: input.binding.desired.records,
299
+ currentRecords: input.currentRecords,
300
+ });
276
301
  if (!current) {
277
302
  changes.push({ action: "create", record: desired, reason: "desired record is missing" });
278
303
  }
279
304
  else if (!recordsEqual(current, desired)) {
305
+ matchedCurrentRecords.add(current);
280
306
  changes.push({ action: "update", record: desired, currentRecord: current, reason: "desired record differs from current provider record" });
281
307
  }
308
+ else {
309
+ matchedCurrentRecords.add(current);
310
+ }
282
311
  }
283
312
  if (input.deleteExtraAllowedRecords) {
284
313
  for (const current of input.currentRecords) {
285
- if (allowedKeys.has(recordKey(current)) && !desiredKeys.has(recordKey(current))) {
314
+ if (allowedKeys.has(recordKey(current)) && !matchedCurrentRecords.has(current)) {
286
315
  changes.push({ action: "delete", record: current, currentRecord: current, reason: "allowlisted record is absent from rollback backup" });
287
316
  }
288
317
  }
289
318
  }
290
- const preservedRecords = input.currentRecords.filter((record) => !desiredKeys.has(recordKey(record)));
319
+ const preservedRecords = input.currentRecords.filter((record) => !matchedCurrentRecords.has(record));
291
320
  return {
292
321
  backup: { domain: input.binding.domain, records: input.currentRecords },
293
322
  changes,
@@ -73,9 +73,9 @@ function isRecipientAllowed(policy, recipient) {
73
73
  return policy.allowedRecipients.includes(recipient) || policy.allowedDomains.includes(recipientDomain(recipient));
74
74
  }
75
75
  function autonomousSentAt(record) {
76
- if (record.status !== "sent" || record.sendMode !== "autonomous")
76
+ if (record.sendMode !== "autonomous")
77
77
  return null;
78
- return record.sentAt ?? record.updatedAt;
78
+ return record.sentAt ?? record.submittedAt ?? record.acceptedAt ?? record.deliveredAt ?? record.failedAt ?? record.updatedAt;
79
79
  }
80
80
  function countRecentAutonomousSends(input) {
81
81
  const startsAt = input.nowMs - input.windowMs;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.473",
3
+ "version": "0.1.0-alpha.475",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",