@inerrata-corporation/errata 2.0.17-dev.3435 → 2.0.17-dev.3450

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.
@@ -18575,6 +18575,8 @@ function generalize(text, opts = {}) {
18575
18575
  init_src();
18576
18576
 
18577
18577
  // src/instance-receipts.ts
18578
+ var INSTANCE_SEMANTIC_EDGE_TYPES = ["CAUSED_BY", "SOLVED_BY"];
18579
+ var INSTANCE_ANCHOR_EDGE_TYPES = ["OCCURS_IN", "DEPENDS_ON", "PERTAIN_TO", "CONCERNS"];
18578
18580
  function receiptMap(attrs) {
18579
18581
  const value = attrs["instanceReceipts"];
18580
18582
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
@@ -19455,8 +19457,8 @@ function generalizeFileRefs(text, fileLexicon, level = 1) {
19455
19457
 
19456
19458
  // src/instance-ingest.ts
19457
19459
  var INSTANCE_LABELS = ["Problem", "Vulnerability", "Solution", "RootCause"];
19458
- var INSTANCE_EDGES = ["CAUSED_BY", "SOLVED_BY"];
19459
- var ANCHOR_EDGES = ["OCCURS_IN", "DEPENDS_ON", "PERTAIN_TO", "CONCERNS"];
19460
+ var INSTANCE_EDGES = INSTANCE_SEMANTIC_EDGE_TYPES;
19461
+ var ANCHOR_EDGES = INSTANCE_ANCHOR_EDGE_TYPES;
19460
19462
  var ANCHOR_TARGET_LABELS = ["Language", "Package", "Domain", "Component"];
19461
19463
  function stripCodebaseScope(scope) {
19462
19464
  const s = { ...scope ?? {} };
package/errata.mjs CHANGED
@@ -26399,7 +26399,7 @@ function parseRestErrorEnvelope(text) {
26399
26399
  return null;
26400
26400
  }
26401
26401
  }
26402
- var asWireCount, INGEST_NODE_CHUNK, E6B_PUBLIC_CONTEXT_SENTINEL, CloudClient, CloudError;
26402
+ var asWireCount, INGEST_NODE_CHUNK, INGEST_EDGE_CHUNK, E6B_PUBLIC_CONTEXT_SENTINEL, CloudClient, CloudError;
26403
26403
  var init_client = __esm({
26404
26404
  "../../packages/cloud-client/src/client.ts"() {
26405
26405
  "use strict";
@@ -26407,6 +26407,7 @@ var init_client = __esm({
26407
26407
  init_src();
26408
26408
  asWireCount = (n) => typeof n === "number" && Number.isFinite(n) && n >= 0 ? Math.floor(n) : null;
26409
26409
  INGEST_NODE_CHUNK = 8;
26410
+ INGEST_EDGE_CHUNK = 32;
26410
26411
  E6B_PUBLIC_CONTEXT_SENTINEL = "00000000-0000-4e6b-8000-000000000e6b";
26411
26412
  CloudClient = class {
26412
26413
  baseUrl;
@@ -26598,42 +26599,45 @@ var init_client = __esm({
26598
26599
  async ingest(batch, opts = {}) {
26599
26600
  const runId = randomUUID();
26600
26601
  const startedAt = Date.now();
26601
- 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);
26602
+ const single = batch.nodes.length <= INGEST_NODE_CHUNK && batch.edges.length <= INGEST_EDGE_CHUNK;
26603
+ let pendingEdges = [...batch.edges];
26604
+ const nodePlan = single ? [] : chunkArray(batch.nodes, INGEST_NODE_CHUNK).map((nodes) => {
26605
+ const ids = new Set(nodes.map((n) => n.id));
26606
+ const edges = pendingEdges.filter((e) => ids.has(e.from) && ids.has(e.to)).slice(0, INGEST_EDGE_CHUNK);
26607
+ const shipped = new Set(edges);
26608
+ pendingEdges = pendingEdges.filter((e) => !shipped.has(e));
26609
+ return { nodes, edges };
26610
+ });
26611
+ const totalChunks = single ? 1 : nodePlan.length + Math.ceil(pendingEdges.length / INGEST_EDGE_CHUNK);
26602
26612
  let chunkIndex = 0;
26603
26613
  const blocked = [];
26604
- const emit = (nodeIds, accepted) => {
26614
+ const emit = async (nodeIds, result) => {
26605
26615
  chunkIndex++;
26606
26616
  const refused = blocked.splice(0, blocked.length);
26607
26617
  const refusedSet = new Set(refused);
26608
- opts.onChunk?.({
26618
+ await opts.onChunk?.({
26609
26619
  chunkIndex,
26610
26620
  totalChunks,
26611
26621
  // Watermark only what actually landed — a blocked node never reached the
26612
26622
  // door, and marking it contributed would lose it permanently and silently.
26613
26623
  nodeIds: refusedSet.size ? nodeIds.filter((id) => !refusedSet.has(id)) : nodeIds,
26614
- accepted,
26624
+ accepted: summarizeIngestResult(result).accepted,
26625
+ result,
26615
26626
  elapsedMs: Date.now() - startedAt,
26616
26627
  ...refused.length ? { blockedNodeIds: refused } : {}
26617
26628
  });
26618
26629
  };
26619
- if (batch.nodes.length <= INGEST_NODE_CHUNK && batch.edges.length <= MAX_EDGES_PER_PAYLOAD) {
26630
+ if (single) {
26620
26631
  const result = await this.ingestWithSplit(batch, batch.nodes, batch.edges, blocked);
26621
26632
  const summary = summarizeIngestResult(result);
26622
- emit(batch.nodes.map((n) => n.id), summary.accepted);
26633
+ await emit(batch.nodes.map((n) => n.id), result);
26623
26634
  return { ...summary, result };
26624
26635
  }
26625
26636
  const merged = { runId, nodes: [], edges: [] };
26626
- let pendingEdges = [...batch.edges];
26627
26637
  const echoedCloudId = /* @__PURE__ */ new Map();
26628
- for (const nodeChunk of chunkArray(batch.nodes, INGEST_NODE_CHUNK)) {
26629
- const ids = new Set(nodeChunk.map((n) => n.id));
26630
- const inChunk = pendingEdges.filter((e) => ids.has(e.from) && ids.has(e.to)).slice(0, MAX_EDGES_PER_PAYLOAD);
26631
- if (inChunk.length > 0) {
26632
- const shipped = new Set(inChunk);
26633
- pendingEdges = pendingEdges.filter((e) => !shipped.has(e));
26634
- }
26638
+ for (const { nodes: nodeChunk, edges: inChunk } of nodePlan) {
26635
26639
  const r = await this.ingestWithSplit(batch, nodeChunk, inChunk, blocked);
26636
- emit(nodeChunk.map((n) => n.id), summarizeIngestResult(r).accepted);
26640
+ await emit(nodeChunk.map((n) => n.id), r);
26637
26641
  merged.nodes.push(...r.nodes);
26638
26642
  merged.edges.push(...r.edges);
26639
26643
  for (const rn of r.nodes) {
@@ -26645,7 +26649,7 @@ var init_client = __esm({
26645
26649
  merged.patternReconciliation = { ...merged.patternReconciliation, ...r.patternReconciliation };
26646
26650
  }
26647
26651
  }
26648
- for (const edgeChunk of chunkArray(pendingEdges, MAX_EDGES_PER_PAYLOAD)) {
26652
+ for (const edgeChunk of chunkArray(pendingEdges, INGEST_EDGE_CHUNK)) {
26649
26653
  const projected = edgeChunk.map((e) => ({
26650
26654
  ...e,
26651
26655
  from: echoedCloudId.get(e.from) ?? e.from,
@@ -26664,11 +26668,11 @@ var init_client = __esm({
26664
26668
  return false;
26665
26669
  });
26666
26670
  if (rewritten.length === 0) {
26667
- emit([], 0);
26671
+ await emit([], { runId, nodes: [], edges: [] });
26668
26672
  continue;
26669
26673
  }
26670
- const r = await this.ingestWire(toWirePayload({ ...batch, nodes: [], edges: rewritten }, randomUUID()));
26671
- emit([], summarizeIngestResult(r).accepted);
26674
+ const r = await this.ingestWithSplit(batch, [], rewritten, blocked);
26675
+ await emit([], r);
26672
26676
  merged.edges.push(...r.edges);
26673
26677
  }
26674
26678
  return { ...summarizeIngestResult(merged), result: merged };
@@ -26697,6 +26701,12 @@ var init_client = __esm({
26697
26701
  } catch (err2) {
26698
26702
  const status = err2 instanceof CloudError ? err2.status : 0;
26699
26703
  const timedOut = status === 502 || status === 504 || status === 408 || status === 0;
26704
+ if (timedOut && nodes.length === 0 && edges.length > 1) {
26705
+ const mid2 = Math.ceil(edges.length / 2);
26706
+ const a2 = await this.ingestWithSplit(batch, [], edges.slice(0, mid2), blocked);
26707
+ const b2 = await this.ingestWithSplit(batch, [], edges.slice(mid2), blocked);
26708
+ return { runId: a2.runId, nodes: [], edges: [...a2.edges, ...b2.edges] };
26709
+ }
26700
26710
  const refusedAtEdge = blocked !== void 0 && isIntermediaryRejection(err2);
26701
26711
  if (refusedAtEdge && nodes.length === 1 && nodes[0]) {
26702
26712
  blocked.push(nodes[0].id);
@@ -82159,7 +82169,7 @@ function createWatchBreaker(deps) {
82159
82169
  }
82160
82170
 
82161
82171
  // src/engine.ts
82162
- var DAEMON_VERSION = true ? "2.0.17-dev.3435" : "2.0.0-alpha.0";
82172
+ var DAEMON_VERSION = true ? "2.0.17-dev.3450" : "2.0.0-alpha.0";
82163
82173
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
82164
82174
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
82165
82175
  var GIT_OP_MUTE_MS = 4e3;
@@ -85045,6 +85055,39 @@ async function linkRegisteredWorkspaces(cfg, loggedInProfile = cfg.activeInstall
85045
85055
 
85046
85056
  // src/instance-receipts.ts
85047
85057
  import { createHash as createHash18 } from "node:crypto";
85058
+ var INSTANCE_SEMANTIC_EDGE_TYPES = ["CAUSED_BY", "SOLVED_BY"];
85059
+ var INSTANCE_ANCHOR_EDGE_TYPES = ["OCCURS_IN", "DEPENDS_ON", "PERTAIN_TO", "CONCERNS"];
85060
+ function edgeReceiptKind(type) {
85061
+ if (INSTANCE_SEMANTIC_EDGE_TYPES.includes(type)) return "semantic";
85062
+ if (INSTANCE_ANCHOR_EDGE_TYPES.includes(type)) return "anchor";
85063
+ return type === "ANCHORED_AT" ? "projectAnchor" : null;
85064
+ }
85065
+ function instanceEdgeReceipts(sent, received, cloudIds) {
85066
+ const wireId = (id) => cloudIds.get(id) ?? id;
85067
+ const edgeKey = (e) => JSON.stringify([wireId(e.from), e.type, wireId(e.to)]);
85068
+ const groupKey = (source, kind) => JSON.stringify([wireId(source), kind]);
85069
+ const accepted = /* @__PURE__ */ new Map();
85070
+ const rejected = /* @__PURE__ */ new Set();
85071
+ for (const edge2 of received) {
85072
+ const key = edgeKey(edge2);
85073
+ if (edge2.decision === "rejected") rejected.add(key);
85074
+ else if (edge2.decision === "created" || edge2.decision === "reinforced") accepted.set(key, (accepted.get(key) ?? 0) + 1);
85075
+ }
85076
+ const groups = /* @__PURE__ */ new Map();
85077
+ for (const edge2 of sent) {
85078
+ const kind = edgeReceiptKind(edge2.type);
85079
+ if (!kind) continue;
85080
+ const group = groupKey(edge2.from, kind);
85081
+ const expected = groups.get(group) ?? /* @__PURE__ */ new Map();
85082
+ const key = edgeKey(edge2);
85083
+ expected.set(key, (expected.get(key) ?? 0) + 1);
85084
+ groups.set(group, expected);
85085
+ }
85086
+ const complete = new Set([...groups].filter(
85087
+ ([, expected]) => [...expected].every(([key, count]) => !rejected.has(key) && (accepted.get(key) ?? 0) >= count)
85088
+ ).map(([group]) => group));
85089
+ return { complete: (source, kind) => complete.has(groupKey(source, kind)) };
85090
+ }
85048
85091
  var FIELDS = ["contributedAtSeq", "cloudNodeId", "anchorsContributedDigest", "projectAnchorsContributedDigest", "semanticEdgesContributedDigest", "shipRefusedSinceSeq", "shipRefusedCount", "shipRefusedReason", "shipRefusedFingerprint"];
85049
85092
  function instanceDestinationKey(input) {
85050
85093
  return createHash18("sha256").update(JSON.stringify([
@@ -85076,8 +85119,20 @@ function updateInstanceReceipt(n, destination, patch, mirrorLegacy) {
85076
85119
  function instanceReceiptSequence(n) {
85077
85120
  return Math.max(n.lastReinforcedAtSeq ?? 0, n.createdAtSeq ?? 0);
85078
85121
  }
85122
+ function acceptedInstanceReceipt(current, sent, decision, destination, mirrorLegacy) {
85123
+ if (!["Problem", "Vulnerability", "Solution", "RootCause"].includes(current.label) || !["created", "reinforced"].includes(decision.decision) || !decision.nodeId) return null;
85124
+ return updateInstanceReceipt(current, destination, {
85125
+ contributedAtSeq: instanceReceiptSequence(sent),
85126
+ cloudNodeId: decision.nodeId,
85127
+ shipRefusedSinceSeq: void 0,
85128
+ shipRefusedCount: void 0,
85129
+ shipRefusedReason: void 0,
85130
+ shipRefusedFingerprint: void 0
85131
+ }, mirrorLegacy);
85132
+ }
85079
85133
 
85080
85134
  // src/multi.ts
85135
+ init_src();
85081
85136
  init_dist();
85082
85137
  init_src5();
85083
85138
  import { existsSync as existsSync34, readFileSync as readFileSync36, unlinkSync as unlinkSync5, writeFileSync as writeFileSync26 } from "node:fs";
@@ -85459,8 +85514,8 @@ function internalSymbolsFor(description, lexiconHas, publicIndex) {
85459
85514
  // src/instance-ingest.ts
85460
85515
  init_generalize_graph();
85461
85516
  var INSTANCE_LABELS = ["Problem", "Vulnerability", "Solution", "RootCause"];
85462
- var INSTANCE_EDGES = ["CAUSED_BY", "SOLVED_BY"];
85463
- var ANCHOR_EDGES = ["OCCURS_IN", "DEPENDS_ON", "PERTAIN_TO", "CONCERNS"];
85517
+ var INSTANCE_EDGES = INSTANCE_SEMANTIC_EDGE_TYPES;
85518
+ var ANCHOR_EDGES = INSTANCE_ANCHOR_EDGE_TYPES;
85464
85519
  var ANCHOR_TARGET_LABELS = ["Language", "Package", "Domain", "Component"];
85465
85520
  function stripCodebaseScope(scope) {
85466
85521
  const s = { ...scope ?? {} };
@@ -90290,10 +90345,24 @@ async function startMultiDaemon(opts = {}) {
90290
90345
  if (project) instances.projectId = project.projectId;
90291
90346
  const shipStartedAt = Date.now();
90292
90347
  await lane("instances", projectName, async () => {
90348
+ const owningStore = (id) => {
90349
+ const w = store.getNode(id);
90350
+ if (w) return { s: store, n: w };
90351
+ const sh = sharedStore.getNode(id);
90352
+ return sh ? { s: sharedStore, n: sh } : null;
90353
+ };
90354
+ const sentById = new Map(instances.nodes.map((n) => [n.id, n]));
90293
90355
  const blocked = /* @__PURE__ */ new Set();
90294
90356
  const res = await client.ingest(instances, {
90295
90357
  onChunk: (p) => {
90296
90358
  for (const id of p.blockedNodeIds ?? []) blocked.add(id);
90359
+ for (const decision of p.result.nodes) {
90360
+ const sent = sentById.get(decision.canonicalId);
90361
+ const owner = owningStore(decision.canonicalId);
90362
+ if (!sent || !owner || blocked.has(sent.id)) continue;
90363
+ const attrs = acceptedInstanceReceipt(owner.n, sent, decision, destinationKey, owner.s === store);
90364
+ if (attrs) owner.s.updateNode(sent.id, { attrs });
90365
+ }
90297
90366
  noteFlushProgress(p.accepted, p.nodeIds.length, `instances ${p.chunkIndex}/${p.totalChunks}`);
90298
90367
  }
90299
90368
  });
@@ -90303,15 +90372,25 @@ async function startMultiDaemon(opts = {}) {
90303
90372
  );
90304
90373
  }
90305
90374
  uploaded += res.accepted;
90306
- const cloudIdByLocal = new Map(
90307
- res.result.nodes.filter((rn) => rn.nodeId).map((rn) => [rn.canonicalId, rn.nodeId])
90308
- );
90309
90375
  const rejected = /* @__PURE__ */ new Map();
90310
90376
  for (const rn of res.result.nodes) {
90311
90377
  if (rn.decision === "rejected") rejected.set(rn.canonicalId, rn.reason ?? "no reason given");
90312
90378
  }
90313
90379
  const rejectedEdges = res.result.edges.filter((re) => re.decision === "rejected");
90314
- const edgeReceiptsComplete = rejectedEdges.length === 0 && res.result.edges.length === instances.edges.length;
90380
+ const cloudIds = /* @__PURE__ */ new Map();
90381
+ for (const node2 of res.result.nodes) {
90382
+ if (isMembraneSaltedId(node2.nodeId)) cloudIds.set(node2.canonicalId, node2.nodeId);
90383
+ }
90384
+ for (const localId of /* @__PURE__ */ new Set([
90385
+ ...Object.keys(instances.anchorDigests),
90386
+ ...Object.keys(instances.projectAnchorDigests),
90387
+ ...Object.keys(instances.semanticEdgeDigests)
90388
+ ])) {
90389
+ const owner = owningStore(localId);
90390
+ const cloudId = owner && instanceReceipt(owner.n, destinationKey)["cloudNodeId"];
90391
+ if (isMembraneSaltedId(cloudId)) cloudIds.set(localId, cloudId);
90392
+ }
90393
+ const edgeReceipts = instanceEdgeReceipts(instances.edges, res.result.edges, cloudIds);
90315
90394
  if (rejectedEdges.length > 0) {
90316
90395
  const byReason = /* @__PURE__ */ new Map();
90317
90396
  for (const re of rejectedEdges) {
@@ -90328,13 +90407,6 @@ async function startMultiDaemon(opts = {}) {
90328
90407
  `[sync\u2192cloud] [${projectName}] instances: ${rejected.size} node(s) REJECTED by the door \u2014 left pending for the next flush: ` + sample.join(" | ")
90329
90408
  );
90330
90409
  }
90331
- const owningStore = (id) => {
90332
- const w = store.getNode(id);
90333
- if (w) return { s: store, n: w };
90334
- const sh = sharedStore.getNode(id);
90335
- if (sh) return { s: sharedStore, n: sh };
90336
- return null;
90337
- };
90338
90410
  let blockedRepeat = 0;
90339
90411
  let rejectedRepeat = 0;
90340
90412
  const nowHeld = [];
@@ -90365,36 +90437,10 @@ async function startMultiDaemon(opts = {}) {
90365
90437
  `[sync\u2192cloud] [${projectName}] instances: HOLDING ${nowHeld.length} node(s) \u2014 refused identically ${SHIP_TERMINAL_REFUSALS}\xD7 on unchanged text; not re-sent until the text changes: ` + nowHeld.map(({ id, reason }) => `${id} "${owningStore(id)?.n.description.slice(0, 60) ?? ""}": ${reason}`).join(" | ")
90366
90438
  );
90367
90439
  }
90368
- await stampBatched(instances.nodes, (n) => {
90369
- const owner = owningStore(n.id);
90370
- if (!owner || !["Problem", "Solution", "RootCause"].includes(owner.n.label)) return;
90371
- if (blocked.has(n.id)) return;
90372
- if (!cloudIdByLocal.has(n.id)) return;
90373
- if (rejected.has(n.id)) return;
90374
- const cloudNodeId = cloudIdByLocal.get(n.id);
90375
- const {
90376
- shipRefusedSinceSeq: _clearedSeq,
90377
- shipRefusedCount: _clearedCount,
90378
- shipRefusedReason: _clearedReason,
90379
- shipRefusedFingerprint: _clearedFp,
90380
- ...carried
90381
- } = instanceReceipt(owner.n, destinationKey);
90382
- owner.s.updateNode(n.id, {
90383
- attrs: updateInstanceReceipt(owner.n, destinationKey, {
90384
- ...carried,
90385
- shipRefusedSinceSeq: void 0,
90386
- shipRefusedCount: void 0,
90387
- shipRefusedReason: void 0,
90388
- shipRefusedFingerprint: void 0,
90389
- contributedAtSeq: instanceReceiptSequence(n),
90390
- cloudNodeId
90391
- }, owner.s === store)
90392
- });
90393
- });
90394
90440
  await stampBatched(Object.entries(instances.anchorDigests), ([localId, dg]) => {
90395
90441
  const owner = owningStore(localId);
90396
90442
  if (!owner) return;
90397
- if (!edgeReceiptsComplete || !instanceReceipt(owner.n, destinationKey)["cloudNodeId"] || rejected.has(localId) || blocked.has(localId)) return;
90443
+ if (!edgeReceipts.complete(localId, "anchor") || !instanceReceipt(owner.n, destinationKey)["cloudNodeId"] || rejected.has(localId) || blocked.has(localId)) return;
90398
90444
  owner.s.updateNode(localId, {
90399
90445
  attrs: updateInstanceReceipt(owner.n, destinationKey, { anchorsContributedDigest: dg }, owner.s === store)
90400
90446
  });
@@ -90402,7 +90448,7 @@ async function startMultiDaemon(opts = {}) {
90402
90448
  await stampBatched(Object.entries(instances.projectAnchorDigests), ([localId, dg]) => {
90403
90449
  const owner = owningStore(localId);
90404
90450
  if (!owner) return;
90405
- if (!edgeReceiptsComplete || !instanceReceipt(owner.n, destinationKey)["cloudNodeId"] || rejected.has(localId) || blocked.has(localId)) return;
90451
+ if (!edgeReceipts.complete(localId, "projectAnchor") || !instanceReceipt(owner.n, destinationKey)["cloudNodeId"] || rejected.has(localId) || blocked.has(localId)) return;
90406
90452
  owner.s.updateNode(localId, {
90407
90453
  attrs: updateInstanceReceipt(owner.n, destinationKey, { projectAnchorsContributedDigest: dg }, owner.s === store)
90408
90454
  });
@@ -90410,7 +90456,7 @@ async function startMultiDaemon(opts = {}) {
90410
90456
  await stampBatched(Object.entries(instances.semanticEdgeDigests), ([localId, dg]) => {
90411
90457
  const owner = owningStore(localId);
90412
90458
  if (!owner) return;
90413
- if (!edgeReceiptsComplete || !instanceReceipt(owner.n, destinationKey)["cloudNodeId"] || rejected.has(localId) || blocked.has(localId)) return;
90459
+ if (!edgeReceipts.complete(localId, "semantic") || !instanceReceipt(owner.n, destinationKey)["cloudNodeId"] || rejected.has(localId) || blocked.has(localId)) return;
90414
90460
  owner.s.updateNode(localId, {
90415
90461
  attrs: updateInstanceReceipt(owner.n, destinationKey, { semanticEdgesContributedDigest: dg }, owner.s === store)
90416
90462
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inerrata-corporation/errata",
3
- "version": "2.0.17-dev.3435",
3
+ "version": "2.0.17-dev.3450",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {