@volter/supercode-teams 0.3.156 → 0.3.157

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/README.md CHANGED
@@ -58,6 +58,14 @@ mail history and individual records can still be large. The
58
58
  [allocation evidence](../../docs/evidence/mail-transcript-stream-2026-10-08.md) names
59
59
  the source boundary and the remaining runtime qualification.
60
60
 
61
+ The connector's redaction memo keeps serialized redacted JSON, with at most
62
+ 200,000 entries and 32 MiB of accounted string payload (two bytes per code unit,
63
+ including keys and digests). An entry over 1 MiB is not retained. Oldest entries
64
+ are evicted to fit; cache misses still go through the redaction worker. These are
65
+ memo bounds, not total connector memory limits. The
66
+ [source investigation](../../docs/evidence/connector-redaction-memo-2026-10-08.md)
67
+ does not attribute the observed process peak or claim a measured runtime improvement.
68
+
61
69
  `supercode teams rpc mail.statistics` reads local storage counts and import/retention receipts. It is refused remotely
62
70
  and through the app door. Writer groups count stored envelope copies, not call attempts or quotas; database page
63
71
  counts exclude physical WAL size. Successful imports record their actual source reads, bytes, batch sizes and time.
@@ -103,14 +103,30 @@ export function foldConnectorState(stateDir){
103
103
  }
104
104
  // What leaves for the server is redacted (@volter/teams/redact), and every digest this connector keeps or sends (what it
105
105
  // observed, a baseline, the carried prefix) is of that form: a digest of the local text named a prefix the server never
106
- // held, every carry was refused, and each session holding a secret went whole on every pass. A message is redacted once
107
- // per process, in the connector's redaction worker: by its digest, nothing kept for one without a secret (most), its
108
- // redacted form for one with. Each loaded message's digest (of the form sent) is taken once and read where needed.
109
- const REDACTED_MEMO=200000;
106
+ // held, every carry was refused, and each session holding a secret went whole on every pass. A message is redacted
107
+ // in the connector's redaction worker, with a bounded digest memo: no body for one without a secret (most), serialized
108
+ // redacted JSON for one with. Evicted or oversized values are redacted again. Each loaded message's digest (of the form
109
+ // sent) is taken once and read where needed. The byte budget counts two bytes per retained string code unit, including
110
+ // keys and digests; the entry bound separately limits Map/record overhead. Neither is a total-process heap bound.
111
+ const REDACTED_MEMO=200000,REDACTED_MEMO_BYTES=32*1024*1024,REDACTED_MEMO_ENTRY_BYTES=1024*1024;
110
112
  const redactions=new Map();
113
+ let redactionBytes=0;
111
114
  const sentDigests=new WeakMap();
112
115
  const digestOf=message=>{let digest=sentDigests.get(message);if(digest===undefined){digest=hash(canonicalJson(message.value));sentDigests.set(message,digest);}return digest;};
113
- const remember=(digest,redacted)=>{if(redactions.size>=REDACTED_MEMO)redactions.clear();redactions.set(digest,redacted);};
116
+ function remember(digest,redacted){
117
+ // Remove the earlier unchanged placeholder even if this redacted body is too large to cache: leaving it would
118
+ // make a later hit reuse the original, unredacted message.
119
+ const previous=redactions.get(digest);
120
+ if(previous){redactionBytes-=previous.bytes;redactions.delete(digest);}
121
+ const kept=redacted?{text:JSON.stringify(redacted.value),digest:redacted.digest}:null;
122
+ const bytes=2*(digest.length+(kept?.digest.length??0)+(kept?.text.length??0));
123
+ if(bytes>REDACTED_MEMO_ENTRY_BYTES)return;
124
+ while(redactions.size>=REDACTED_MEMO||redactionBytes+bytes>REDACTED_MEMO_BYTES){
125
+ const oldest=redactions.keys().next().value;
126
+ redactionBytes-=redactions.get(oldest).bytes;redactions.delete(oldest);
127
+ }
128
+ redactions.set(digest,{kept,bytes});redactionBytes+=bytes;
129
+ }
114
130
  // A capture part is one request, and the server refuses a body over 8 MiB (volter-teams request-json.mjs): a part
115
131
  // holds at most PART_MESSAGES messages (the contract's bound) and PART_BYTES of them, so a run of large tool results
116
132
  // splits across parts instead of failing the whole session. A single message larger than that goes alone.
@@ -881,7 +897,7 @@ export class TeamsConnector {
881
897
  const out=new Array(raw.length),missing=[];
882
898
  raw.forEach((message,index)=>{
883
899
  const text=canonicalJson(message.value),digest=hash(text);
884
- if(redactions.has(digest)){const kept=redactions.get(digest);out[index]=kept?{...message,value:kept.value}:message;sentDigests.set(out[index],kept?kept.digest:digest);return;}
900
+ if(redactions.has(digest)){const {kept}=redactions.get(digest);out[index]=kept?{...message,value:JSON.parse(kept.text)}:message;sentDigests.set(out[index],kept?kept.digest:digest);return;}
885
901
  if(held?.has(digest)){remember(digest,null);out[index]=message;sentDigests.set(message,digest);return;}
886
902
  missing.push([index,digest,text]);
887
903
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/supercode-teams",
3
- "version": "0.3.156",
3
+ "version": "0.3.157",
4
4
  "type": "module",
5
5
  "description": "A machine daemon serving this machine's doors, and a local or remote Teams server for shared session discovery, OpenTelemetry ingestion and access to enrolled machines.",
6
6
  "license": "MIT",