@inerrata-corporation/errata 2.0.2-dev.227 → 2.0.2-dev.245

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.
Files changed (2) hide show
  1. package/errata.mjs +129 -15
  2. package/package.json +1 -1
package/errata.mjs CHANGED
@@ -21643,6 +21643,7 @@ var init_oauth = __esm({
21643
21643
 
21644
21644
  // ../../packages/cloud-client/src/client.ts
21645
21645
  import { randomUUID } from "node:crypto";
21646
+ import { gzipSync } from "node:zlib";
21646
21647
  function wirePerContext(value) {
21647
21648
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
21648
21649
  const out2 = {};
@@ -21840,6 +21841,11 @@ function normalizeSolution(input) {
21840
21841
  validationSource: input.validationSource
21841
21842
  };
21842
21843
  }
21844
+ function isIntermediaryRejection(err2) {
21845
+ if (!(err2 instanceof CloudError)) return false;
21846
+ if (err2.status < 400 || err2.status >= 500) return false;
21847
+ return /<!DOCTYPE html|<html/i.test(err2.message);
21848
+ }
21843
21849
  function chunkArray(items, size) {
21844
21850
  if (items.length <= size) return items.length ? [[...items]] : [];
21845
21851
  const out2 = [];
@@ -21857,7 +21863,7 @@ function provenanceHeaders(provenance) {
21857
21863
  ...provenance.agentModel ? { "x-inerrata-agent-model": provenance.agentModel } : {}
21858
21864
  };
21859
21865
  }
21860
- var asWireCount, INGEST_NODE_CHUNK, CloudClient, CloudError;
21866
+ var asWireCount, INGEST_NODE_CHUNK, COMPRESS_MIN_BYTES, CloudClient, CloudError;
21861
21867
  var init_client = __esm({
21862
21868
  "../../packages/cloud-client/src/client.ts"() {
21863
21869
  "use strict";
@@ -21865,6 +21871,7 @@ var init_client = __esm({
21865
21871
  init_src();
21866
21872
  asWireCount = (n) => typeof n === "number" && Number.isFinite(n) && n >= 0 ? Math.floor(n) : null;
21867
21873
  INGEST_NODE_CHUNK = 8;
21874
+ COMPRESS_MIN_BYTES = 4096;
21868
21875
  CloudClient = class {
21869
21876
  baseUrl;
21870
21877
  apiKey;
@@ -21879,6 +21886,8 @@ var init_client = __esm({
21879
21886
  daemonVersion;
21880
21887
  daemonChannel;
21881
21888
  provenance;
21889
+ /** Cleared for the process once a server proves it cannot inflate (see `json`). */
21890
+ compressRequests;
21882
21891
  constructor(opts) {
21883
21892
  this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
21884
21893
  if (opts.apiKey !== void 0) this.apiKey = opts.apiKey;
@@ -21892,6 +21901,7 @@ var init_client = __esm({
21892
21901
  this.timeoutMs = opts.timeoutMs ?? 15e3;
21893
21902
  this.daemonVersion = opts.daemonVersion;
21894
21903
  this.daemonChannel = opts.daemonChannel;
21904
+ this.compressRequests = opts.compressRequests ?? true;
21895
21905
  this.provenance = opts.provenance ?? {
21896
21906
  clientProduct: "inerrata_cloud_client",
21897
21907
  clientKind: "sdk",
@@ -22029,18 +22039,24 @@ var init_client = __esm({
22029
22039
  const startedAt = Date.now();
22030
22040
  const totalChunks = Math.max(1, Math.ceil(batch.nodes.length / INGEST_NODE_CHUNK)) + Math.ceil(Math.max(0, batch.edges.length - MAX_EDGES_PER_PAYLOAD) / MAX_EDGES_PER_PAYLOAD);
22031
22041
  let chunkIndex = 0;
22042
+ const blocked = [];
22032
22043
  const emit = (nodeIds, accepted) => {
22033
22044
  chunkIndex++;
22045
+ const refused = blocked.splice(0, blocked.length);
22046
+ const refusedSet = new Set(refused);
22034
22047
  opts.onChunk?.({
22035
22048
  chunkIndex,
22036
22049
  totalChunks,
22037
- nodeIds,
22050
+ // Watermark only what actually landed — a blocked node never reached the
22051
+ // door, and marking it contributed would lose it permanently and silently.
22052
+ nodeIds: refusedSet.size ? nodeIds.filter((id) => !refusedSet.has(id)) : nodeIds,
22038
22053
  accepted,
22039
- elapsedMs: Date.now() - startedAt
22054
+ elapsedMs: Date.now() - startedAt,
22055
+ ...refused.length ? { blockedNodeIds: refused } : {}
22040
22056
  });
22041
22057
  };
22042
22058
  if (batch.nodes.length <= INGEST_NODE_CHUNK && batch.edges.length <= MAX_EDGES_PER_PAYLOAD) {
22043
- const result = await this.ingestWithSplit(batch, batch.nodes, batch.edges);
22059
+ const result = await this.ingestWithSplit(batch, batch.nodes, batch.edges, blocked);
22044
22060
  const summary = summarizeIngestResult(result);
22045
22061
  emit(batch.nodes.map((n) => n.id), summary.accepted);
22046
22062
  return { ...summary, result };
@@ -22055,7 +22071,7 @@ var init_client = __esm({
22055
22071
  const shipped = new Set(inChunk);
22056
22072
  pendingEdges = pendingEdges.filter((e) => !shipped.has(e));
22057
22073
  }
22058
- const r = await this.ingestWithSplit(batch, nodeChunk, inChunk);
22074
+ const r = await this.ingestWithSplit(batch, nodeChunk, inChunk, blocked);
22059
22075
  emit(nodeChunk.map((n) => n.id), summarizeIngestResult(r).accepted);
22060
22076
  merged.nodes.push(...r.nodes);
22061
22077
  merged.edges.push(...r.edges);
@@ -22098,16 +22114,21 @@ var init_client = __esm({
22098
22114
  * a verdict about the payload — splitting it would just re-send a rejected batch
22099
22115
  * N more times — and a 409/413 has its own handling upstream.
22100
22116
  */
22101
- async ingestWithSplit(batch, nodes, edges) {
22117
+ async ingestWithSplit(batch, nodes, edges, blocked) {
22102
22118
  try {
22103
22119
  return await this.ingestWire(toWirePayload({ ...batch, nodes, edges }, randomUUID()));
22104
22120
  } catch (err2) {
22105
22121
  const status = err2 instanceof CloudError ? err2.status : 0;
22106
22122
  const timedOut = status === 502 || status === 504 || status === 408 || status === 0;
22107
- if (!timedOut || nodes.length <= 1) throw err2;
22123
+ const refusedAtEdge = blocked !== void 0 && isIntermediaryRejection(err2);
22124
+ if (refusedAtEdge && nodes.length === 1 && nodes[0]) {
22125
+ blocked.push(nodes[0].id);
22126
+ return { runId: randomUUID(), nodes: [], edges: [] };
22127
+ }
22128
+ if (!timedOut && !refusedAtEdge || nodes.length <= 1) throw err2;
22108
22129
  const mid = Math.ceil(nodes.length / 2);
22109
- const a = await this.ingestWithSplit(batch, nodes.slice(0, mid), edges);
22110
- const b = await this.ingestWithSplit(batch, nodes.slice(mid), []);
22130
+ const a = await this.ingestWithSplit(batch, nodes.slice(0, mid), edges, blocked);
22131
+ const b = await this.ingestWithSplit(batch, nodes.slice(mid), [], blocked);
22111
22132
  return {
22112
22133
  runId: a.runId,
22113
22134
  nodes: [...a.nodes, ...b.nodes],
@@ -22409,15 +22430,43 @@ var init_client = __esm({
22409
22430
  const token = opts?.bearerToken ?? await this.authToken();
22410
22431
  if (token) headers["authorization"] = `Bearer ${token}`;
22411
22432
  }
22433
+ let payload;
22434
+ if (body2 !== void 0) {
22435
+ const raw2 = JSON.stringify(body2);
22436
+ if (this.compressRequests && Buffer.byteLength(raw2) >= COMPRESS_MIN_BYTES) {
22437
+ payload = gzipSync(Buffer.from(raw2));
22438
+ headers["content-encoding"] = "gzip";
22439
+ } else {
22440
+ payload = raw2;
22441
+ }
22442
+ }
22412
22443
  const ac = new AbortController();
22413
22444
  const tid = setTimeout(() => ac.abort(), this.timeoutMs);
22414
22445
  try {
22415
22446
  const res = await this.fetchFn(url2, {
22416
22447
  method,
22417
22448
  headers,
22418
- body: body2 === void 0 ? void 0 : JSON.stringify(body2),
22449
+ body: payload,
22419
22450
  signal: ac.signal
22420
22451
  });
22452
+ if (res.status === 400 && headers["content-encoding"] === "gzip") {
22453
+ delete headers["content-encoding"];
22454
+ const retry = await this.fetchFn(url2, {
22455
+ method,
22456
+ headers,
22457
+ body: JSON.stringify(body2),
22458
+ signal: ac.signal
22459
+ });
22460
+ if (!retry.ok) {
22461
+ const text = await retry.text();
22462
+ throw new CloudError(
22463
+ `${method} ${path2} failed: HTTP ${retry.status} ${text.slice(0, 200)}`,
22464
+ retry.status
22465
+ );
22466
+ }
22467
+ this.compressRequests = false;
22468
+ return await retry.json();
22469
+ }
22421
22470
  if (!res.ok) {
22422
22471
  const text = await res.text();
22423
22472
  throw new CloudError(
@@ -52458,7 +52507,7 @@ function startLoopLagMonitor(thresholdMs = 1e3) {
52458
52507
  }
52459
52508
 
52460
52509
  // src/engine.ts
52461
- var DAEMON_VERSION = true ? "2.0.2-dev.227" : "2.0.0-alpha.0";
52510
+ var DAEMON_VERSION = true ? "2.0.2-dev.245" : "2.0.0-alpha.0";
52462
52511
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
52463
52512
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
52464
52513
  var GIT_OP_MUTE_MS = 4e3;
@@ -54729,6 +54778,39 @@ init_config();
54729
54778
  // src/update.ts
54730
54779
  import { spawn as spawn2 } from "node:child_process";
54731
54780
  var PACKAGE_NAME = "@inerrata-corporation/errata";
54781
+ function parse3(v) {
54782
+ const m = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z][0-9A-Za-z.-]*))?/.exec(v.trim());
54783
+ if (!m) return null;
54784
+ return {
54785
+ base: [Number(m[1]), Number(m[2]), Number(m[3])],
54786
+ pre: m[4] === void 0 ? null : m[4].split(/[.-]/).map((p) => /^\d+$/.test(p) ? Number(p) : p)
54787
+ };
54788
+ }
54789
+ function compareVersions(a, b) {
54790
+ const pa = parse3(a);
54791
+ const pb = parse3(b);
54792
+ if (!pa || !pb) return 0;
54793
+ const [aMaj, aMin, aPatch] = pa.base;
54794
+ const [bMaj, bMin, bPatch] = pb.base;
54795
+ if (aMaj !== bMaj) return aMaj - bMaj;
54796
+ if (aMin !== bMin) return aMin - bMin;
54797
+ if (aPatch !== bPatch) return aPatch - bPatch;
54798
+ if (pa.pre === null && pb.pre === null) return 0;
54799
+ if (pa.pre === null) return 1;
54800
+ if (pb.pre === null) return -1;
54801
+ for (let i2 = 0; i2 < Math.max(pa.pre.length, pb.pre.length); i2++) {
54802
+ const x = pa.pre[i2];
54803
+ const y = pb.pre[i2];
54804
+ if (x === void 0) return -1;
54805
+ if (y === void 0) return 1;
54806
+ if (x === y) continue;
54807
+ if (typeof x === "number" && typeof y === "number") return x - y;
54808
+ if (typeof x === "number") return -1;
54809
+ if (typeof y === "number") return 1;
54810
+ return x < y ? -1 : 1;
54811
+ }
54812
+ return 0;
54813
+ }
54732
54814
  async function latestPublished(channel, timeoutMs = 4e3) {
54733
54815
  const ctrl = new AbortController();
54734
54816
  const timer = setTimeout(() => ctrl.abort(), timeoutMs);
@@ -54748,10 +54830,14 @@ async function latestPublished(channel, timeoutMs = 4e3) {
54748
54830
  }
54749
54831
  async function checkForUpdate(channel) {
54750
54832
  const latest = await latestPublished(channel);
54833
+ const direction = latest === null ? 0 : compareVersions(latest, DAEMON_VERSION);
54751
54834
  return {
54752
54835
  current: DAEMON_VERSION,
54753
54836
  latest,
54754
- updateAvailable: latest !== null && latest !== DAEMON_VERSION
54837
+ // `compareVersions` returns 0 for unparseable input, so an unrecognizable tag
54838
+ // is treated as "nothing to do" rather than gambling on a direction.
54839
+ updateAvailable: latest !== null && direction > 0,
54840
+ downgradeAvailable: latest !== null && direction < 0
54755
54841
  };
54756
54842
  }
54757
54843
  var POLL_INTERVAL_MS = {
@@ -55733,6 +55819,11 @@ async function startMultiDaemon(opts = {}) {
55733
55819
  if (!local) continue;
55734
55820
  store.updateNode(localId, { attrs: { ...local.attrs, contributedAtSeq: seq } });
55735
55821
  }
55822
+ if (p.blockedNodeIds?.length) {
55823
+ console.log(
55824
+ `[sync\u2192cloud] [${projectName}] context: ${p.blockedNodeIds.length} node(s) refused by an edge/CDN filter on content \u2014 left pending, will retry`
55825
+ );
55826
+ }
55736
55827
  noteFlushProgress(p.accepted, p.nodeIds.length, `context ${p.chunkIndex}/${p.totalChunks}`);
55737
55828
  }
55738
55829
  });
@@ -55762,9 +55853,18 @@ async function startMultiDaemon(opts = {}) {
55762
55853
  if (instances) {
55763
55854
  if (project) instances.projectId = project.projectId;
55764
55855
  await lane("instances", projectName, async () => {
55856
+ const blocked = /* @__PURE__ */ new Set();
55765
55857
  const res = await client.ingest(instances, {
55766
- onChunk: (p) => noteFlushProgress(p.accepted, p.nodeIds.length, `instances ${p.chunkIndex}/${p.totalChunks}`)
55858
+ onChunk: (p) => {
55859
+ for (const id of p.blockedNodeIds ?? []) blocked.add(id);
55860
+ noteFlushProgress(p.accepted, p.nodeIds.length, `instances ${p.chunkIndex}/${p.totalChunks}`);
55861
+ }
55767
55862
  });
55863
+ if (blocked.size > 0) {
55864
+ console.log(
55865
+ `[sync\u2192cloud] [${projectName}] instances: ${blocked.size} node(s) refused by an edge/CDN filter on content \u2014 left pending, will retry. First: ${store.getNode([...blocked][0])?.description.slice(0, 120) ?? [...blocked][0]}`
55866
+ );
55867
+ }
55768
55868
  uploaded += res.accepted;
55769
55869
  const seq = store.currentIngestSeq();
55770
55870
  const cloudIdByLocal = new Map(
@@ -55773,6 +55873,7 @@ async function startMultiDaemon(opts = {}) {
55773
55873
  for (const n of instances.nodes) {
55774
55874
  const local = store.getNode(n.id);
55775
55875
  if (!local || !["Problem", "Solution", "RootCause"].includes(local.label)) continue;
55876
+ if (blocked.has(n.id)) continue;
55776
55877
  const cloudNodeId = cloudIdByLocal.get(n.id);
55777
55878
  store.updateNode(n.id, {
55778
55879
  attrs: {
@@ -57214,9 +57315,11 @@ async function cmdUpdate(args2) {
57214
57315
  const cfg = loadConfig();
57215
57316
  let channel = cfg.updateChannel;
57216
57317
  let checkOnly = false;
57318
+ let allowDowngrade = false;
57217
57319
  for (let i2 = 0; i2 < args2.length; i2++) {
57218
57320
  const a = args2[i2];
57219
57321
  if (a === "--check") checkOnly = true;
57322
+ else if (a === "--allow-downgrade") allowDowngrade = true;
57220
57323
  else if (a === "--channel") {
57221
57324
  const v = args2[++i2];
57222
57325
  if (v === "dev" || v === "latest") channel = v;
@@ -57237,8 +57340,19 @@ async function cmdUpdate(args2) {
57237
57340
  return;
57238
57341
  }
57239
57342
  if (!status.updateAvailable) {
57240
- console.log(`errata is up to date \u2014 ${status.current} (channel: ${channel})`);
57241
- return;
57343
+ if (status.downgradeAvailable) {
57344
+ console.log(
57345
+ `channel '${channel}' points at ${status.latest}, which is OLDER than the running ${status.current}.`
57346
+ );
57347
+ console.log(` not installing \u2014 a downgrade past a store migration cannot open its own graph.`);
57348
+ console.log(` if this is a deliberate rollback: errata update --allow-downgrade`);
57349
+ if (!allowDowngrade) return;
57350
+ console.log(`
57351
+ --allow-downgrade given; proceeding.`);
57352
+ } else {
57353
+ console.log(`errata is up to date \u2014 ${status.current} (channel: ${channel})`);
57354
+ return;
57355
+ }
57242
57356
  }
57243
57357
  console.log(`update available: ${status.current} \u2192 ${status.latest} (channel: ${channel})`);
57244
57358
  if (checkOnly) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inerrata-corporation/errata",
3
- "version": "2.0.2-dev.227",
3
+ "version": "2.0.2-dev.245",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {