@volter/teams 0.1.57 → 0.1.58

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/teams",
3
- "version": "0.1.57",
3
+ "version": "0.1.58",
4
4
  "type": "module",
5
5
  "description": "Volter Teams, the neutral team plane: machine identity and daemon core, the local door and server link, admission from machine grants, product doors, and the Teams server's neutral routes (company RFC 0008).",
6
6
  "license": "MIT",
package/server/core.mjs CHANGED
@@ -80,6 +80,10 @@ export async function maintain(api){const now=api.clock.nowMs();
80
80
  AND NOT EXISTS (SELECT 1 FROM session_artifacts a WHERE a.team_id=u.team_id AND a.upload_id=u.id)
81
81
  AND NOT EXISTS (SELECT 1 FROM continuations c WHERE c.team_id=u.team_id AND instr(c.value_json,u.id)>0)
82
82
  AND NOT EXISTS (SELECT 1 FROM captures c WHERE c.team_id=u.team_id AND c.state='staging' AND instr(c.begin_json,u.id)>0)`,params:[upload.team_id,upload.id]}]);
83
+ const unusedBlobs = await api.store.query(`SELECT team_id,sha256 FROM artifact_blobs b
84
+ WHERE NOT EXISTS (SELECT 1 FROM artifact_chunk_refs r WHERE r.team_id=b.team_id AND r.sha256=b.sha256) LIMIT 50`);
85
+ for (const blob of unusedBlobs) await api.store.transaction([{sql:`DELETE FROM artifact_blobs AS b WHERE team_id=? AND sha256=?
86
+ AND NOT EXISTS (SELECT 1 FROM artifact_chunk_refs r WHERE r.team_id=b.team_id AND r.sha256=b.sha256)`,params:[blob.team_id,blob.sha256]}]);
83
87
  // Every capture writes its session a new version (its messages are carried, not copied: schema 9), so versions pile
84
88
  // up (hundreds per session) until listing them outgrew a Worker's memory. Kept: each session's current version, the newest
85
89
  // version from each of its sources (the next capture and a correlation read that one), and every version an
@@ -87,6 +91,10 @@ export async function maintain(api){const now=api.clock.nowMs();
87
91
  const stale=await api.store.query(`SELECT v.team_id,v.session_id,v.revision FROM session_versions v JOIN sessions s ON s.team_id=v.team_id AND s.id=v.session_id
88
92
  WHERE v.revision<>s.revision
89
93
  AND EXISTS (SELECT 1 FROM session_versions n WHERE n.team_id=v.team_id AND n.session_id=v.session_id AND n.revision>v.revision AND json_extract(n.source_projection_json,'$.source_id') IS json_extract(v.source_projection_json,'$.source_id'))
94
+ AND (NOT EXISTS (SELECT 1 FROM session_artifacts a WHERE a.team_id=v.team_id AND a.session_id=v.session_id AND a.revision=v.revision AND a.provenance='capture')
95
+ OR EXISTS (SELECT 1 FROM session_artifacts a JOIN session_versions n ON n.team_id=a.team_id AND n.session_id=a.session_id AND n.revision=a.revision
96
+ WHERE a.team_id=v.team_id AND a.session_id=v.session_id AND a.revision>v.revision AND a.provenance='capture'
97
+ AND json_extract(n.source_projection_json,'$.source_id') IS json_extract(v.source_projection_json,'$.source_id')))
90
98
  AND NOT EXISTS (SELECT 1 FROM cursors c, json_each(c.snapshot_json) j WHERE c.team_id=v.team_id AND c.kind IN ('list','search') AND c.expires_at>? AND json_extract(j.value,'$[0]')=v.session_id AND json_extract(j.value,'$[1]')=v.revision)
91
99
  AND NOT EXISTS (SELECT 1 FROM cursors c WHERE c.team_id=v.team_id AND c.kind='history' AND c.expires_at>? AND json_extract(c.snapshot_json,'$.session_id')=v.session_id AND json_extract(c.snapshot_json,'$.revision')=v.revision)
92
100
  LIMIT ${VERSION_PRUNE_BATCH}`,[now,now]);
@@ -111,7 +119,7 @@ export async function maintain(api){const now=api.clock.nowMs();
111
119
  // Presence reconciliation can INSERT. Never put it ahead of the deletes
112
120
  // that make room when the database is full.
113
121
  await reconcileMachinePresence(api);
114
- const result={pruned_versions:stale.length,pruned_parts:parts.length,pruned_idempotency:expired.length,pruned_uploads:orphaned.length,backlog:orphaned.length>=10||expired.length>=1000||stale.length>=VERSION_PRUNE_BATCH||parts.length>=PART_PRUNE_BATCH};
122
+ const result={pruned_versions:stale.length,pruned_parts:parts.length,pruned_idempotency:expired.length,pruned_uploads:orphaned.length,pruned_blobs:unusedBlobs.length,backlog:unusedBlobs.length>=50||orphaned.length>=10||expired.length>=1000||stale.length>=VERSION_PRUNE_BATCH||parts.length>=PART_PRUNE_BATCH};
115
123
  if(result.pruned_versions||result.pruned_parts||result.pruned_idempotency||result.pruned_uploads)console.log(`maintain: pruned ${result.pruned_uploads} unreachable bundles, ${result.pruned_idempotency} expired replay records, ${result.pruned_parts} parts, ${result.pruned_versions} versions${result.backlog?' (backlog)':''}`);
116
124
  return result;}
117
125
  /** Session versions `maintain` removes per run: small enough for one Durable Object alarm; a backlog runs again in seconds. */
@@ -0,0 +1,20 @@
1
+ // New captures share immutable chunk contents. Legacy uploads stay readable
2
+ // without copying their bytes during migration; ordinary retention retires them.
3
+ export default `
4
+ CREATE TABLE artifact_blobs (
5
+ team_id TEXT NOT NULL, sha256 TEXT NOT NULL, bytes BLOB NOT NULL,
6
+ PRIMARY KEY(team_id,sha256), CHECK(length(bytes)<=1048576)
7
+ ) STRICT;
8
+ CREATE TABLE artifact_chunk_refs (
9
+ team_id TEXT NOT NULL, upload_id TEXT NOT NULL, chunk_index INTEGER NOT NULL,
10
+ sha256 TEXT NOT NULL, PRIMARY KEY(team_id,upload_id,chunk_index),
11
+ FOREIGN KEY(team_id,upload_id) REFERENCES uploads(team_id,id) ON DELETE CASCADE,
12
+ FOREIGN KEY(team_id,sha256) REFERENCES artifact_blobs(team_id,sha256)
13
+ ) STRICT;
14
+ CREATE INDEX artifact_chunk_refs_blob ON artifact_chunk_refs(team_id,sha256);
15
+ CREATE VIEW artifact_chunk_data AS
16
+ SELECT team_id,upload_id,chunk_index,sha256,bytes FROM artifact_chunks
17
+ UNION ALL
18
+ SELECT r.team_id,r.upload_id,r.chunk_index,r.sha256,b.bytes
19
+ FROM artifact_chunk_refs r JOIN artifact_blobs b ON b.team_id=r.team_id AND b.sha256=r.sha256;
20
+ `;
@@ -9,7 +9,8 @@ import m007 from './007.mjs';
9
9
  import m008 from './008.mjs';
10
10
  import m009 from './009.mjs';
11
11
  import m010 from './010.mjs';
12
- export const MIGRATIONS = [m001, m002, m003, m004, m005, m006, m007, m008, m009, m010];
12
+ import m011 from './011.mjs';
13
+ export const MIGRATIONS = [m001, m002, m003, m004, m005, m006, m007, m008, m009, m010, m011];
13
14
  /** A message row's `until_revision` while its session's current version still holds it (schema 9). */
14
15
  export const OPEN_REVISION = Number.MAX_SAFE_INTEGER;
15
16
  /** SQL: message row `m` belongs to the version at revision ?1 (bind the revision twice). */