@volter/supercode-teams 0.2.61 → 0.2.62

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/supercode-teams",
3
- "version": "0.2.61",
3
+ "version": "0.2.62",
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",
@@ -1,3 +1,13 @@
1
+ import { MESSAGE_AT, OPEN_REVISION } from '@volter/teams/server/migrations';
2
+
3
+ /**
4
+ * A new version of `sessionId` at `revision` holding exactly the messages of `from`'s version (a correlation's primary):
5
+ * the session's open rows are closed at it and `from`'s rows are copied as rows of this version alone (schema 9).
6
+ */
7
+ function copyVersion(all, run, teamId, from, sessionId, revision) {
8
+ run('UPDATE messages SET until_revision=? WHERE team_id=? AND session_id=? AND until_revision=?', [revision, teamId, sessionId, OPEN_REVISION]);
9
+ for (const m of all(`SELECT * FROM messages m WHERE m.team_id=? AND m.session_id=? AND ${MESSAGE_AT()} ORDER BY ordinal`, [teamId, from.id, from.revision, from.revision])) run('INSERT INTO messages(team_id,session_id,revision,ordinal,id,source_id,native_key,recorded_at,value_json,search_text) VALUES (?,?,?,?,?,?,?,?,?,?)', [teamId, sessionId, revision, m.ordinal, m.id, m.source_id, m.native_key, m.recorded_at, m.value_json, m.search_text]);
10
+ }
1
11
  function normalizeSearch(value) { return String(value ?? "").normalize("NFC").toLowerCase(); }
2
12
  function contentText(content) { return typeof content === "string" ? content : Array.isArray(content) ? content.filter((x) => x?.type === "text").map((x) => x.text).join("\n") : ""; }
3
13
  function fail(message, code, status) { throw Object.assign(new Error(message), { code, status }); }
@@ -33,12 +43,12 @@ export function correlateSessions(db,args){
33
43
  const primary=views.reduce((best,row)=>rank(row)>rank(best)||rank(row)===rank(best)&&String(row.source_id).localeCompare(String(best.source_id))<0?row:best,views[0]);
34
44
  const team=get('SELECT journal_revision FROM teams WHERE id=?',[args.teamId]),revision=team.journal_revision+1;
35
45
  const originSources=all(`SELECT DISTINCT source_id FROM origins WHERE team_id=? AND session_id IN (${members.map(()=>'?').join(',')})`,[args.teamId,...members.map(row=>row.id)]).map(row=>row.source_id);const descriptor={...JSON.parse(primary.descriptor_json),id:canonical.id,revision,source_ids:[...new Set(originSources)].sort()};
36
- const comparable=new Map();let sourceConflict=false;for(const view of views)for(const message of all('SELECT native_key,value_json FROM messages WHERE team_id=? AND session_id=? AND revision=?',[args.teamId,view.id,view.revision])){if(!message.native_key)continue;const value=JSON.parse(message.value_json),semantic=JSON.stringify({role:value.role,content:value.content});const previous=comparable.get(message.native_key);if(previous!==undefined&&previous!==semantic)sourceConflict=true;else comparable.set(message.native_key,semantic);}if(sourceConflict)descriptor.coverage={...descriptor.coverage,reasons:[...new Set([...(descriptor.coverage?.reasons??[]),'source_conflict'])]};
46
+ const comparable=new Map();let sourceConflict=false;for(const view of views)for(const message of all(`SELECT native_key,value_json FROM messages m WHERE m.team_id=? AND m.session_id=? AND ${MESSAGE_AT()}`,[args.teamId,view.id,view.revision,view.revision])){if(!message.native_key)continue;const value=JSON.parse(message.value_json),semantic=JSON.stringify({role:value.role,content:value.content});const previous=comparable.get(message.native_key);if(previous!==undefined&&previous!==semantic)sourceConflict=true;else comparable.set(message.native_key,semantic);}if(sourceConflict)descriptor.coverage={...descriptor.coverage,reasons:[...new Set([...(descriptor.coverage?.reasons??[]),'source_conflict'])]};
37
47
  for(const member of members)run('UPDATE sessions SET canonical_id=? WHERE team_id=? AND id=?',[member.id===canonical.id?null:canonical.id,args.teamId,member.id]);
38
48
  run('UPDATE sessions SET revision=? WHERE team_id=? AND id=?',[revision,args.teamId,canonical.id]);
39
49
  run('INSERT INTO correlations(team_id,id,left_session_id,right_session_id,canonical_id,policy_json,evidence_json,revision) VALUES (?,?,?,?,?,?,?,?)',[args.teamId,args.correlationId,leftInput.id,rightInput.id,canonical.id,JSON.stringify({reader:'all_constituents'}),JSON.stringify(args.evidence),revision]);
40
50
  run('INSERT INTO session_versions(team_id,session_id,revision,descriptor_json,source_projection_json,search_title,sort_at,created_at) VALUES (?,?,?,?,?,?,?,?)',[args.teamId,canonical.id,revision,JSON.stringify(descriptor),primary.source_projection_json,primary.search_title,primary.sort_at,now]);
41
- for(const m of all('SELECT * FROM messages WHERE team_id=? AND session_id=? AND revision=? ORDER BY ordinal',[args.teamId,primary.id,primary.revision]))run('INSERT INTO messages(team_id,session_id,revision,ordinal,id,source_id,native_key,recorded_at,value_json,search_text) VALUES (?,?,?,?,?,?,?,?,?,?)',[args.teamId,canonical.id,revision,m.ordinal,m.id,m.source_id,m.native_key,m.recorded_at,m.value_json,m.search_text]);
51
+ copyVersion(all,run,args.teamId,primary,canonical.id,revision);
42
52
  run('UPDATE teams SET journal_revision=?,auth_epoch=auth_epoch+1 WHERE id=?',[revision,args.teamId]);
43
53
  run('INSERT INTO journal(team_id,revision,session_id,kind,created_at) VALUES (?,?,?,?,?)',[args.teamId,revision,canonical.id,'metadata',now]);
44
54
  const response={version:1,context:args.context,data:descriptor};
@@ -87,16 +97,17 @@ export function ingestOtlpBatch(db, args) {
87
97
  run('INSERT INTO session_versions(team_id,session_id,revision,descriptor_json,source_projection_json,search_title,sort_at,created_at) VALUES (?,?,?,?,?,?,?,?)',[args.teamId,sessionId,journalRevision,JSON.stringify(descriptor),JSON.stringify({source_id:args.sourceId,kind:'telemetry',native_session_id:items[0].decoded.native_session_id,decoder:items[0].decoded.decoder}),normalizeSearch(descriptor.title),descriptor.sort_at,now]);
88
98
  let ordinal = 0;
89
99
  if (prior) {
90
- const priorMessages = all('SELECT * FROM messages WHERE team_id=? AND session_id=? AND revision=? ORDER BY ordinal',[args.teamId,sessionId,session.revision]);
91
- for (const message of priorMessages) run('INSERT INTO messages(team_id,session_id,revision,ordinal,id,source_id,native_key,recorded_at,value_json,search_text) VALUES (?,?,?,?,?,?,?,?,?,?)',[args.teamId,sessionId,journalRevision,ordinal++,message.id,message.source_id,message.native_key,message.recorded_at,message.value_json,message.search_text]);
92
- }
100
+ // The current version's messages stay open into this one (schema 9); new events follow them.
101
+ run(`UPDATE messages SET until_revision=? WHERE team_id=? AND session_id=? AND ${MESSAGE_AT('messages')}`,[OPEN_REVISION,args.teamId,sessionId,session.revision,session.revision]);
102
+ ordinal = (get(`SELECT MAX(ordinal) last FROM messages m WHERE m.team_id=? AND m.session_id=? AND ${MESSAGE_AT()}`,[args.teamId,sessionId,session.revision,session.revision])?.last ?? -1) + 1;
103
+ } else run('UPDATE messages SET until_revision=? WHERE team_id=? AND session_id=? AND until_revision=?',[journalRevision,args.teamId,sessionId,OPEN_REVISION]);
93
104
  for (const item of messages) {
94
105
  const decoded=item.decoded, nativeKey=decoded.identity?.native_event_id ?? item.evidenceId;
95
106
  let messageId = decoded.identity?.native_event_id ? get('SELECT id FROM message_ids WHERE team_id=? AND source_id=? AND harness=? AND native_id=? AND native_key=?',[args.teamId,args.sourceId,decoded.harness,decoded.native_session_id,nativeKey])?.id : null;
96
- if (messageId && get('SELECT 1 ok FROM messages WHERE team_id=? AND session_id=? AND revision=? AND native_key=?',[args.teamId,sessionId,journalRevision,nativeKey])) continue;
107
+ if (messageId && get(`SELECT 1 ok FROM messages m WHERE m.team_id=? AND m.session_id=? AND ${MESSAGE_AT()} AND m.native_key=?`,[args.teamId,sessionId,journalRevision,journalRevision,nativeKey])) continue;
97
108
  messageId ??= item.proposedMessageId;
98
109
  if (decoded.identity?.native_event_id) run('INSERT OR IGNORE INTO message_ids(team_id,source_id,harness,native_id,native_key,id) VALUES (?,?,?,?,?,?)',[args.teamId,args.sourceId,decoded.harness,decoded.native_session_id,nativeKey,messageId]);
99
- run('INSERT INTO messages(team_id,session_id,revision,ordinal,id,source_id,native_key,recorded_at,value_json,search_text) VALUES (?,?,?,?,?,?,?,?,?,?)',[args.teamId,sessionId,journalRevision,ordinal++,messageId,args.sourceId,nativeKey,decoded.recorded_at,JSON.stringify(decoded.message),normalizeSearch(contentText(decoded.message.content))]);
110
+ run('INSERT INTO messages(team_id,session_id,revision,ordinal,id,source_id,native_key,recorded_at,value_json,search_text,until_revision) VALUES (?,?,?,?,?,?,?,?,?,?,?)',[args.teamId,sessionId,journalRevision,ordinal++,messageId,args.sourceId,nativeKey,decoded.recorded_at,JSON.stringify(decoded.message),normalizeSearch(contentText(decoded.message.content)),OPEN_REVISION]);
100
111
  }
101
112
  run('INSERT INTO journal(team_id,revision,session_id,kind,created_at) VALUES (?,?,?,?,?)',[args.teamId,journalRevision,sessionId,'capture',now]); changedSessions.push({sessionId,revision:journalRevision});
102
113
  }
@@ -112,7 +123,7 @@ export function ingestOtlpBatch(db, args) {
112
123
  journalRevision++;const originSources=all(`SELECT DISTINCT source_id FROM origins WHERE team_id=? AND session_id IN (${family.map(()=>'?').join(',')})`,[args.teamId,...family.map(row=>row.id)]).map(row=>row.source_id);const descriptor={...JSON.parse(primary.descriptor_json),id:canonicalId,revision:journalRevision,source_ids:[...new Set(originSources)].sort()};
113
124
  run('UPDATE sessions SET revision=? WHERE team_id=? AND id=?',[journalRevision,args.teamId,canonicalId]);
114
125
  run('INSERT INTO session_versions(team_id,session_id,revision,descriptor_json,source_projection_json,search_title,sort_at,created_at) VALUES (?,?,?,?,?,?,?,?)',[args.teamId,canonicalId,journalRevision,JSON.stringify(descriptor),primary.source_projection_json,primary.search_title,primary.sort_at,now]);
115
- for(const m of all('SELECT * FROM messages WHERE team_id=? AND session_id=? AND revision=? ORDER BY ordinal',[args.teamId,primary.id,primary.revision]))run('INSERT INTO messages(team_id,session_id,revision,ordinal,id,source_id,native_key,recorded_at,value_json,search_text) VALUES (?,?,?,?,?,?,?,?,?,?)',[args.teamId,canonicalId,journalRevision,m.ordinal,m.id,m.source_id,m.native_key,m.recorded_at,m.value_json,m.search_text]);
126
+ copyVersion(all,run,args.teamId,primary,canonicalId,journalRevision);
116
127
  run('INSERT INTO journal(team_id,revision,session_id,kind,created_at) VALUES (?,?,?,?,?)',[args.teamId,journalRevision,canonicalId,'capture',now]);changedSessions.push({sessionId:canonicalId,revision:journalRevision});
117
128
  }
118
129
  const currentCounters = JSON.parse(authorized.counters_json ?? '{}');
@@ -12,6 +12,7 @@ import { normalizeSearch, textFromContent, cursorHash, cursorToken } from './cat
12
12
  import { captureDigest, captureMessages, handleOtlpRoute, sha256, validateArtifactName } from './ingest.mjs';
13
13
  import { handleCatalogEvents, handleSessionEvents, sessionNames } from './event-routes.mjs';
14
14
  import { exact } from '@volter/teams/server/request-json';
15
+ import { MESSAGE_AT, OPEN_REVISION } from '@volter/teams/server/migrations';
15
16
  import { delegatedSessionOf, looksDelegated } from '@volter/teams/server/delegated';
16
17
  import { renderSourceCollectorConfig } from './otlp/collector-config.mjs';
17
18
  import { attachmentActionRoute, createAttachment, liveGrantRoute, machineEffectRoute } from './effects.mjs';
@@ -146,8 +147,8 @@ async function listSessions(api,res,auth,url,requestId,search){
146
147
  );
147
148
  if(cursorKey)statements.push({kind:'query',sql:`SELECT s.*,v.revision version_revision,v.descriptor_json,v.sort_at FROM ${cursorVersions} JOIN session_versions v ON v.team_id=? AND v.session_id=json_extract(j.value,'$[0]') AND v.revision=json_extract(j.value,'$[1]') JOIN sessions s ON s.team_id=v.team_id AND s.id=v.session_id WHERE s.deleted_at IS NULL AND v.revision<>s.revision`,params:[...cursorKey,auth.team_id]});
148
149
  if(search){
149
- statements.push({kind:'query',sql:'SELECT m.id,m.session_id,m.revision,m.recorded_at,m.search_text FROM sessions s JOIN messages m ON m.team_id=s.team_id AND m.session_id=s.id AND m.revision=s.revision WHERE s.team_id=? AND s.deleted_at IS NULL',params:[auth.team_id]});
150
- if(cursorKey)statements.push({kind:'query',sql:`SELECT m.id,m.session_id,m.revision,m.recorded_at,m.search_text FROM ${cursorVersions} JOIN messages m ON m.team_id=? AND m.session_id=json_extract(j.value,'$[0]') AND m.revision=json_extract(j.value,'$[1]') JOIN sessions s ON s.team_id=m.team_id AND s.id=m.session_id WHERE s.deleted_at IS NULL AND m.revision<>s.revision`,params:[...cursorKey,auth.team_id]});
150
+ statements.push({kind:'query',sql:'SELECT m.id,m.session_id,s.revision revision,m.recorded_at,m.search_text FROM sessions s JOIN messages m ON m.team_id=s.team_id AND m.session_id=s.id AND m.revision<=s.revision AND s.revision<COALESCE(m.until_revision,m.revision+1) WHERE s.team_id=? AND s.deleted_at IS NULL',params:[auth.team_id]});
151
+ if(cursorKey)statements.push({kind:'query',sql:`SELECT m.id,m.session_id,json_extract(j.value,'$[1]') revision,m.recorded_at,m.search_text FROM ${cursorVersions} JOIN messages m ON m.team_id=? AND m.session_id=json_extract(j.value,'$[0]') AND m.revision<=json_extract(j.value,'$[1]') AND json_extract(j.value,'$[1]')<COALESCE(m.until_revision,m.revision+1) JOIN sessions s ON s.team_id=m.team_id AND s.id=m.session_id WHERE s.deleted_at IS NULL AND json_extract(j.value,'$[1]')<>s.revision`,params:[...cursorKey,auth.team_id]});
151
152
  }
152
153
  const values=await api.store.snapshot(statements),offset=cursorKey?1:0,cursor=cursorKey?values[0]:null,team=values[offset],credential=values[offset+1],membership=values[offset+2],currentRows=values[offset+3],allGrants=values[offset+4];
153
154
  let at=offset+5;const olderRows=cursorKey?values[at++]:[],allMessages=search?[...values[at++],...(cursorKey?values[at++]:[])]:[];
@@ -208,7 +209,8 @@ async function correlateRoute(api,req,res,auth,path,requestId){
208
209
  async function mutateSession(api,req,res,auth,id,requestId){const seed=await api.store.one('SELECT * FROM sessions WHERE team_id=? AND id=?',[auth.team_id,id]),root=await canonicalSession(api,auth.team_id,seed),canonicalId=root?.id??id;const row=await api.store.one('SELECT s.*,v.descriptor_json,v.source_projection_json,v.search_title,v.sort_at FROM sessions s JOIN session_versions v ON v.team_id=s.team_id AND v.session_id=s.id AND v.revision=s.revision WHERE s.team_id=? AND s.id=?',[auth.team_id,canonicalId]);if(!row)fail(404,'not_found','not found');const members=await constituents(api,auth.team_id,row);for(const member of members)if(!isAdmin(auth)&&!canManage(auth,member,await grants(api,auth.team_id,member.id)))fail(404,'not_found','not found');const expected=Number(String(req.headers['if-match']??'').replaceAll('"',''));if(!Number.isSafeInteger(expected))fail(400,'invalid_request','If-Match is required');if(expected!==row.revision)fail(409,'revision_mismatch','session revision changed');const boundaryNow=api.clock.nowMs(),revisionParam={$journalRevision:true},guards=[...authGuards(auth,boundaryNow),{sql:'SELECT 1 ok FROM sessions WHERE team_id=? AND id=? AND revision=? AND deleted_at IS NULL',params:[auth.team_id,row.id,expected],code:'revision_mismatch',status:409,message:'session revision changed'}];
209
210
  for(const member of members)guards.push(managerGuard(auth,member.id,boundaryNow,{governance:true}));
210
211
  if(req.method==='DELETE'){const deletion=[];for(const member of members)deletion.push({sql:'UPDATE sessions SET deleted_at=?,revision=? WHERE team_id=? AND id=?',params:[boundaryNow,revisionParam,auth.team_id,member.id]},{sql:'UPDATE origins SET deleted_at=? WHERE team_id=? AND session_id=?',params:[boundaryNow,auth.team_id,member.id]},{sql:'DELETE FROM session_versions WHERE team_id=? AND session_id=?',params:[auth.team_id,member.id]});deletion.push({sql:'UPDATE teams SET journal_revision=?,auth_epoch=auth_epoch+1 WHERE id=?',params:[revisionParam,auth.team_id]},{sql:'INSERT INTO journal(team_id,revision,session_id,kind,created_at) VALUES (?,?,?,?,?)',params:[auth.team_id,revisionParam,row.id,'delete',boundaryNow]});const {revision}=await guardedMutation(api,res,auth,guards,deletion,{status:204,body:null,targetId:row.id,journal:true});api.events.publish(auth.team_id,{type:'session_deleted',cursor:String(revision),revision,session_id:row.id});return send(res,204,null,requestId);}
211
- const body=await parseJson(req);exact(body,['title']);if(body.title!==null&&typeof body.title!=='string')fail(400,'invalid_request','title must be string or null');const descriptor={...projection(row),title:body.title};delete descriptor.revision;const oldMessages=await api.store.query('SELECT * FROM messages WHERE team_id=? AND session_id=? AND revision=? ORDER BY ordinal',[auth.team_id,row.id,row.revision]);const statements=[{sql:'UPDATE sessions SET revision=? WHERE team_id=? AND id=? AND revision=?',params:[revisionParam,auth.team_id,row.id,expected]},{sql:'INSERT INTO session_versions(team_id,session_id,revision,descriptor_json,source_projection_json,search_title,sort_at,created_at) VALUES (?,?,?,?,?,?,?,?)',params:[auth.team_id,row.id,revisionParam,{$journalJson:descriptor},row.source_projection_json,normalizeSearch(body.title??''),row.sort_at,boundaryNow]}];for(const x of oldMessages)statements.push({sql:'INSERT INTO messages(team_id,session_id,revision,ordinal,id,source_id,native_key,recorded_at,value_json,search_text) VALUES (?,?,?,?,?,?,?,?,?,?)',params:[auth.team_id,row.id,revisionParam,x.ordinal,x.id,x.source_id,x.native_key,x.recorded_at,x.value_json,x.search_text]});statements.push({sql:'UPDATE teams SET journal_revision=? WHERE id=?',params:[revisionParam,auth.team_id]},{sql:'INSERT INTO journal(team_id,revision,session_id,kind,created_at) VALUES (?,?,?,?,?)',params:[auth.team_id,revisionParam,row.id,'metadata',boundaryNow]});const storedResponse=result(api,auth,{...descriptor,revision:'__JOURNAL_REVISION__'}),{revision}=await guardedMutation(api,res,auth,guards,statements,{status:200,body:storedResponse,targetId:row.id,journal:true}),responseDescriptor={...descriptor,revision};api.events.publish(auth.team_id,{type:'session_changed',cursor:String(revision),revision,session_id:row.id,reason:'metadata',read_required:true});send(res,200,result(api,auth,responseDescriptor),requestId,{ETag:`"${revision}"`});}
212
+ const body=await parseJson(req);exact(body,['title']);if(body.title!==null&&typeof body.title!=='string')fail(400,'invalid_request','title must be string or null');const descriptor={...projection(row),title:body.title};delete descriptor.revision;const statements=[{sql:'UPDATE sessions SET revision=? WHERE team_id=? AND id=? AND revision=?',params:[revisionParam,auth.team_id,row.id,expected]},{sql:'INSERT INTO session_versions(team_id,session_id,revision,descriptor_json,source_projection_json,search_title,sort_at,created_at) VALUES (?,?,?,?,?,?,?,?)',params:[auth.team_id,row.id,revisionParam,{$journalJson:descriptor},row.source_projection_json,normalizeSearch(body.title??''),row.sort_at,boundaryNow]}];// The new version holds the same messages: the current version's rows stay open into it (schema 9), none copied.
213
+ statements.push({sql:`UPDATE messages SET until_revision=? WHERE team_id=? AND session_id=? AND ${MESSAGE_AT('messages')}`,params:[OPEN_REVISION,auth.team_id,row.id,row.revision,row.revision]});statements.push({sql:'UPDATE teams SET journal_revision=? WHERE id=?',params:[revisionParam,auth.team_id]},{sql:'INSERT INTO journal(team_id,revision,session_id,kind,created_at) VALUES (?,?,?,?,?)',params:[auth.team_id,revisionParam,row.id,'metadata',boundaryNow]});const storedResponse=result(api,auth,{...descriptor,revision:'__JOURNAL_REVISION__'}),{revision}=await guardedMutation(api,res,auth,guards,statements,{status:200,body:storedResponse,targetId:row.id,journal:true}),responseDescriptor={...descriptor,revision};api.events.publish(auth.team_id,{type:'session_changed',cursor:String(revision),revision,session_id:row.id,reason:'metadata',read_required:true});send(res,200,result(api,auth,responseDescriptor),requestId,{ETag:`"${revision}"`});}
212
214
  async function readSession(api,res,auth,id,url,requestId){
213
215
  const query=qobj(url);
214
216
  validateReadQuery(query);
@@ -294,8 +296,20 @@ async function captureCommit(api,req,res,auth,sourceId,captureId,requestId){cons
294
296
  const sid=origin?.session_id??api.ids.next('session'),parentOrigin=commit.parent_native_id?await api.store.one('SELECT session_id FROM origins WHERE team_id=? AND source_id=? AND harness=? AND native_id=? AND deleted_at IS NULL',[auth.team_id,sourceId,cap.harness,commit.parent_native_id]):null,revisionParam={$journalRevision:true},recorded=commit.recorded_activity_at,boundaryNow=api.clock.nowMs(),sortAt=recorded??boundaryNow,descriptor={id:sid,revision:'__JOURNAL_REVISION__',title:commit.title,harness:cap.harness,user:{principal_id:source.kind==='connector'?source.custodian_id:null,reported_user:commit.reported_user,status:source.kind==='connector'?'verified':commit.reported_user?'reported':'unknown',mapping_id:null},repository:commit.repository,parent_id:parentOrigin?.session_id??null,continued_from:lineage?.source_session_id??null,recorded_activity_at:recorded,received_at:boundaryNow,sort_at:sortAt,activity_basis:recorded==null?'received':'recorded',source_ids:[sourceId],machine_ids:source.machine_id?[source.machine_id]:[],coverage:commit.coverage,bindings:[],actions:{}},statements=[];
295
297
  if(!origin)statements.push({sql:'INSERT INTO sessions(team_id,id,harness,custodian_id,visibility,revision,created_at) VALUES (?,?,?,?,?,?,?)',params:[auth.team_id,sid,cap.harness,source.custodian_id,lineage?'restricted':begin.visibility,revisionParam,boundaryNow]},{sql:'INSERT INTO origins(team_id,source_id,harness,native_id,session_id,generation,sequence,digest) VALUES (?,?,?,?,?,?,?,?)',params:[auth.team_id,sourceId,cap.harness,cap.native_id,sid,cap.generation,cap.sequence,commit.digest]});else statements.push({sql:'UPDATE sessions SET revision=? WHERE team_id=? AND id=?',params:[revisionParam,auth.team_id,sid]},{sql:'UPDATE origins SET generation=?,sequence=?,digest=? WHERE team_id=? AND source_id=? AND harness=? AND native_id=?',params:[cap.generation,cap.sequence,commit.digest,auth.team_id,sourceId,cap.harness,cap.native_id]});
296
298
  if(lineage){lineageValue.new_session_id=sid;const operationRow=await api.store.one('SELECT value_json FROM operations WHERE team_id=? AND id=?',[auth.team_id,lineageValue.operation_id]),operationValue=JSON.parse(operationRow.value_json);operationValue.result={...operationValue.result,new_session_id:sid};statements.push({sql:"UPDATE continuations SET value_json=? WHERE team_id=? AND id=? AND state='running' AND value_json=?",params:[JSON.stringify(lineageValue),auth.team_id,lineage.id,lineage.value_json]},{sql:'UPDATE operations SET value_json=? WHERE team_id=? AND id=?',params:[JSON.stringify(operationValue),auth.team_id,lineageValue.operation_id]});lineageGuards.push({sql:"SELECT 1 ok FROM continuations WHERE team_id=? AND id=? AND state='running' AND value_json=?",params:[auth.team_id,lineage.id,lineage.value_json],code:'conflict',status:409,message:'continuation lineage changed'});}
297
- statements.push({sql:'INSERT INTO session_versions(team_id,session_id,revision,descriptor_json,source_projection_json,search_title,sort_at,created_at) VALUES (?,?,?,?,?,?,?,?)',params:[auth.team_id,sid,revisionParam,{$journalJsonValue:descriptor},JSON.stringify({source_id:sourceId}),normalizeSearch(commit.title??''),sortAt,boundaryNow]});let ordinal=0;for(const m of messages){let mid=(await api.store.one('SELECT id FROM message_ids WHERE team_id=? AND source_id=? AND harness=? AND native_id=? AND native_key=?',[auth.team_id,sourceId,cap.harness,cap.native_id,m.native_key]))?.id;if(!mid){mid=api.ids.next('message');statements.push({sql:'INSERT INTO message_ids(team_id,source_id,harness,native_id,native_key,id) VALUES (?,?,?,?,?,?)',params:[auth.team_id,sourceId,cap.harness,cap.native_id,m.native_key,mid]});}statements.push({sql:'INSERT INTO messages(team_id,session_id,revision,ordinal,id,source_id,native_key,recorded_at,value_json,search_text) VALUES (?,?,?,?,?,?,?,?,?,?)',params:[auth.team_id,sid,revisionParam,ordinal++,mid,sourceId,m.native_key,m.recorded_at,JSON.stringify(m.value),normalizeSearch(textFromContent(m.value.content))]});}
298
- const bundle=commit.native_bundle??null,legacyBundle=commit.artifacts.length?captureId:null;for(let index=0;index<commit.artifacts.length;index++){const a=commit.artifacts[index],file=bundle?.files.find(x=>x.upload_id===a.upload_id);statements.push({sql:'UPDATE uploads SET manifest_json=?,committed=1 WHERE team_id=? AND id=?',params:[JSON.stringify(a),auth.team_id,a.upload_id]},{sql:'INSERT INTO session_artifacts(team_id,session_id,revision,bundle_id,upload_id,format,provenance,file_role,fidelity,residue_json) VALUES (?,?,?,?,?,?,?,?,?,?)',params:[auth.team_id,sid,revisionParam,bundle?.id??legacyBundle,a.upload_id,bundle?.harness??cap.harness,'capture',file?.role??(index===0?'primary':'bundle'),bundle?.fidelity??'semantic',JSON.stringify(bundle?.residue??['native_bundle_metadata_unavailable'])]});}const storedReceipt={source_id:sourceId,capture_id:captureId,session_id:sid,generation:cap.generation,sequence:cap.sequence,digest:commit.digest,revision:'__JOURNAL_REVISION__',duplicate:false};statements.push({sql:"UPDATE captures SET state='committed',commit_json=?,receipt_json=? WHERE team_id=? AND id=?",params:[JSON.stringify(commit),{$journalJsonValue:storedReceipt},auth.team_id,captureId]},{sql:'DELETE FROM capture_parts WHERE team_id=? AND capture_id=?',params:[auth.team_id,captureId]},{sql:'UPDATE sources SET last_received_at=? WHERE team_id=? AND id=?',params:[boundaryNow,auth.team_id,sourceId]},{sql:'UPDATE teams SET journal_revision=? WHERE id=?',params:[revisionParam,auth.team_id]},{sql:'INSERT INTO journal(team_id,revision,session_id,kind,created_at) VALUES (?,?,?,?,?)',params:[auth.team_id,revisionParam,sid,'capture',boundaryNow]});const guards=[{sql:'SELECT 1 ok FROM credentials WHERE id=? AND source_id=? AND revoked_at IS NULL AND (expires_at IS NULL OR expires_at>?)',params:[auth.credential_id,sourceId,boundaryNow],code:'authentication_required',status:401,message:'source credential expired or revoked'},...lineageGuards,{sql:"SELECT 1 ok FROM captures c JOIN sources s ON s.team_id=c.team_id AND s.id=c.source_id LEFT JOIN machines m ON m.team_id=s.team_id AND m.id=s.machine_id LEFT JOIN sync_rules r ON r.team_id=c.team_id AND r.id=json_extract(c.begin_json,'$.rule_id') WHERE c.team_id=? AND c.id=? AND c.source_id=? AND c.state='staging' AND s.state='active' AND (s.kind!='connector' OR (m.revoked_at IS NULL AND r.machine_id=m.id AND r.source_id=s.id AND r.state='active' AND r.revision=json_extract(c.begin_json,'$.rule_revision') AND r.visibility=json_extract(c.begin_json,'$.visibility')))",params:[auth.team_id,captureId,sourceId],code:'conflict',status:409,message:'capture authorization changed'}],storedResponse=result(api,auth,storedReceipt),{revision}=await guardedMutation(api,res,auth,guards,statements,{status:200,body:storedResponse,targetId:captureId,journal:true}),receipt={...storedReceipt,revision};api.events.publish(auth.team_id,{type:'session_changed',cursor:String(revision),revision,session_id:sid,reason:'capture',read_required:true});send(res,200,result(api,auth,receipt),requestId);
299
+ statements.push({sql:'INSERT INTO session_versions(team_id,session_id,revision,descriptor_json,source_projection_json,search_title,sort_at,created_at) VALUES (?,?,?,?,?,?,?,?)',params:[auth.team_id,sid,revisionParam,{$journalJsonValue:descriptor},JSON.stringify({source_id:sourceId}),normalizeSearch(commit.title??''),sortAt,boundaryNow]});// Only what changed is written (schema 9): a message the session's current version holds unchanged at the same place
300
+ // stays open into this version; one that changed or went is closed at it; a new one is written. A capture of a long
301
+ // session that gained one message writes one row, not the whole session again.
302
+ const carryGuards=[],currentRevision=origin?(await api.store.one('SELECT revision FROM sessions WHERE team_id=? AND id=?',[auth.team_id,sid]))?.revision??null:null;
303
+ const held=new Map();if(currentRevision!=null)for(const row of await api.store.query(`SELECT revision,ordinal,id,source_id,native_key,recorded_at,value_json,until_revision FROM messages m WHERE m.team_id=? AND m.session_id=? AND ${MESSAGE_AT()}`,[auth.team_id,sid,currentRevision,currentRevision]))held.set(row.ordinal,row);
304
+ // Another commit to this session between this read and the write would make the carried rows wrong: refused, retried.
305
+ if(currentRevision!=null)carryGuards.push({sql:'SELECT 1 ok FROM sessions WHERE team_id=? AND id=? AND revision=?',params:[auth.team_id,sid,currentRevision],code:'revision_mismatch',status:409,message:'session changed during capture; retry'});
306
+ const close=row=>{if(row.until_revision===OPEN_REVISION)statements.push({sql:'UPDATE messages SET until_revision=? WHERE team_id=? AND session_id=? AND revision=? AND ordinal=?',params:[revisionParam,auth.team_id,sid,row.revision,row.ordinal]});};
307
+ let ordinal=0;for(const m of messages){const value=JSON.stringify(m.value),old=held.get(ordinal);held.delete(ordinal);
308
+ if(old&&old.source_id===sourceId&&old.native_key===m.native_key&&old.recorded_at===m.recorded_at&&old.value_json===value){if(old.until_revision!==OPEN_REVISION)statements.push({sql:'UPDATE messages SET until_revision=? WHERE team_id=? AND session_id=? AND revision=? AND ordinal=?',params:[OPEN_REVISION,auth.team_id,sid,old.revision,old.ordinal]});ordinal++;continue;}
309
+ if(old)close(old);
310
+ let mid=(await api.store.one('SELECT id FROM message_ids WHERE team_id=? AND source_id=? AND harness=? AND native_id=? AND native_key=?',[auth.team_id,sourceId,cap.harness,cap.native_id,m.native_key]))?.id;if(!mid){mid=api.ids.next('message');statements.push({sql:'INSERT INTO message_ids(team_id,source_id,harness,native_id,native_key,id) VALUES (?,?,?,?,?,?)',params:[auth.team_id,sourceId,cap.harness,cap.native_id,m.native_key,mid]});}statements.push({sql:'INSERT INTO messages(team_id,session_id,revision,ordinal,id,source_id,native_key,recorded_at,value_json,search_text,until_revision) VALUES (?,?,?,?,?,?,?,?,?,?,?)',params:[auth.team_id,sid,revisionParam,ordinal++,mid,sourceId,m.native_key,m.recorded_at,value,normalizeSearch(textFromContent(m.value.content)),OPEN_REVISION]});}
311
+ for(const row of held.values())close(row);
312
+ const bundle=commit.native_bundle??null,legacyBundle=commit.artifacts.length?captureId:null;for(let index=0;index<commit.artifacts.length;index++){const a=commit.artifacts[index],file=bundle?.files.find(x=>x.upload_id===a.upload_id);statements.push({sql:'UPDATE uploads SET manifest_json=?,committed=1 WHERE team_id=? AND id=?',params:[JSON.stringify(a),auth.team_id,a.upload_id]},{sql:'INSERT INTO session_artifacts(team_id,session_id,revision,bundle_id,upload_id,format,provenance,file_role,fidelity,residue_json) VALUES (?,?,?,?,?,?,?,?,?,?)',params:[auth.team_id,sid,revisionParam,bundle?.id??legacyBundle,a.upload_id,bundle?.harness??cap.harness,'capture',file?.role??(index===0?'primary':'bundle'),bundle?.fidelity??'semantic',JSON.stringify(bundle?.residue??['native_bundle_metadata_unavailable'])]});}const storedReceipt={source_id:sourceId,capture_id:captureId,session_id:sid,generation:cap.generation,sequence:cap.sequence,digest:commit.digest,revision:'__JOURNAL_REVISION__',duplicate:false};statements.push({sql:"UPDATE captures SET state='committed',commit_json=?,receipt_json=? WHERE team_id=? AND id=?",params:[JSON.stringify(commit),{$journalJsonValue:storedReceipt},auth.team_id,captureId]},{sql:'DELETE FROM capture_parts WHERE team_id=? AND capture_id=?',params:[auth.team_id,captureId]},{sql:'UPDATE sources SET last_received_at=? WHERE team_id=? AND id=?',params:[boundaryNow,auth.team_id,sourceId]},{sql:'UPDATE teams SET journal_revision=? WHERE id=?',params:[revisionParam,auth.team_id]},{sql:'INSERT INTO journal(team_id,revision,session_id,kind,created_at) VALUES (?,?,?,?,?)',params:[auth.team_id,revisionParam,sid,'capture',boundaryNow]});const guards=[{sql:'SELECT 1 ok FROM credentials WHERE id=? AND source_id=? AND revoked_at IS NULL AND (expires_at IS NULL OR expires_at>?)',params:[auth.credential_id,sourceId,boundaryNow],code:'authentication_required',status:401,message:'source credential expired or revoked'},...lineageGuards,...carryGuards,{sql:"SELECT 1 ok FROM captures c JOIN sources s ON s.team_id=c.team_id AND s.id=c.source_id LEFT JOIN machines m ON m.team_id=s.team_id AND m.id=s.machine_id LEFT JOIN sync_rules r ON r.team_id=c.team_id AND r.id=json_extract(c.begin_json,'$.rule_id') WHERE c.team_id=? AND c.id=? AND c.source_id=? AND c.state='staging' AND s.state='active' AND (s.kind!='connector' OR (m.revoked_at IS NULL AND r.machine_id=m.id AND r.source_id=s.id AND r.state='active' AND r.revision=json_extract(c.begin_json,'$.rule_revision') AND r.visibility=json_extract(c.begin_json,'$.visibility')))",params:[auth.team_id,captureId,sourceId],code:'conflict',status:409,message:'capture authorization changed'}],storedResponse=result(api,auth,storedReceipt),{revision}=await guardedMutation(api,res,auth,guards,statements,{status:200,body:storedResponse,targetId:captureId,journal:true}),receipt={...storedReceipt,revision};api.events.publish(auth.team_id,{type:'session_changed',cursor:String(revision),revision,session_id:sid,reason:'capture',read_required:true});send(res,200,result(api,auth,receipt),requestId);
299
313
  }
300
314
 
301
315
  /**
@@ -5,7 +5,7 @@
5
5
  // them on the object's own SQLite.
6
6
  import { ingestOtlpBatch, correlateSessions } from './arc2-transactions.mjs';
7
7
  import { claimEffect, getEffect, listEffects, reportEffect } from './effects-transactions.mjs';
8
- import { MIGRATIONS } from '@volter/teams/server/migrations';
8
+ import { MIGRATIONS, MESSAGE_AT } from '@volter/teams/server/migrations';
9
9
 
10
10
  /** Bring a database to the newest schema. `version()`/`setVersion(n)` name where the host records it. */
11
11
  export function migrate({ exec, tx, version, setVersion }) {
@@ -85,12 +85,12 @@ export function createStoreHandlers({ all, get, run, tx }) {
85
85
  }else{
86
86
  revision=requestedRevision??session.revision;
87
87
  if(!get('SELECT 1 ok FROM session_versions WHERE team_id=? AND session_id=? AND revision=?',[teamId,session.id,revision]))domainError(404,'not_found','session revision not found');
88
- ordinals=all('SELECT ordinal FROM messages WHERE team_id=? AND session_id=? AND revision=? ORDER BY ordinal',[teamId,session.id,revision]).map(row=>row.ordinal);
88
+ ordinals=all(`SELECT ordinal FROM messages m WHERE m.team_id=? AND m.session_id=? AND ${MESSAGE_AT()} ORDER BY ordinal`,[teamId,session.id,revision,revision]).map(row=>row.ordinal);
89
89
  }
90
90
  const pageOrdinals=ordinals.slice(-tail),remaining=ordinals.slice(0,-tail);
91
91
  // A page is a run of one revision's ordinals, read as a range: a hosted SQLite binds at most 100 variables, and a
92
92
  // page of more messages than that, as an IN list, failed ("too many SQL variables").
93
- const rows=pageOrdinals.length?all('SELECT * FROM messages WHERE team_id=? AND session_id=? AND revision=? AND ordinal>=? AND ordinal<=? ORDER BY ordinal',[teamId,session.id,revision,pageOrdinals[0],pageOrdinals[pageOrdinals.length-1]]).filter(row=>pageOrdinals.includes(row.ordinal)):[];
93
+ const rows=pageOrdinals.length?all(`SELECT * FROM messages m WHERE m.team_id=? AND m.session_id=? AND ${MESSAGE_AT()} AND m.ordinal>=? AND m.ordinal<=? ORDER BY m.ordinal`,[teamId,session.id,revision,revision,pageOrdinals[0],pageOrdinals[pageOrdinals.length-1]]).filter(row=>pageOrdinals.includes(row.ordinal)):[];
94
94
  const version=get('SELECT descriptor_json FROM session_versions WHERE team_id=? AND session_id=? AND revision=?',[teamId,session.id,revision]);
95
95
  if(!version)domainError(404,'not_found','session revision not found');
96
96
  let descriptorJson=version.descriptor_json;