@volter/supercode-teams 0.3.20 → 0.3.21

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/bin/teams.mjs CHANGED
@@ -33,7 +33,7 @@ import { appendFileSync, createWriteStream, readFileSync, writeFileSync } from '
33
33
  import { spawnSync } from 'node:child_process';
34
34
  import { fileURLToPath, pathToFileURL } from 'node:url';
35
35
  import { SupercodeHarnessClient } from '@volter/supercode-harness-sdk';
36
- import { noteStep, slowLogPath, timeCommand } from '@volter/supercode-harness-sdk/slow-log';
36
+ import { noteStep, rotatedLogs, slowLogPath, timeCommand } from '@volter/supercode-harness-sdk/slow-log';
37
37
 
38
38
  // A Windows scheduled task keeps no output (launchd and systemd name log files), so its unit names one here.
39
39
  if (process.env.SUPERCODE_TEAMS_LOG) {
@@ -197,7 +197,7 @@ async function main() {
197
197
  return at;
198
198
  })();
199
199
  let text = '';
200
- for (const file of [`${slowLogPath()}.1`, slowLogPath()]) {
200
+ for (const file of [...rotatedLogs(), slowLogPath()]) {
201
201
  try { text += readFileSync(file, 'utf8'); } catch (error) { if (error.code !== 'ENOENT') throw error; }
202
202
  }
203
203
  const groups = new Map();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/supercode-teams",
3
- "version": "0.3.20",
3
+ "version": "0.3.21",
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",
@@ -440,7 +440,7 @@ async function captureCommit(api,req,res,auth,sourceId,captureId,requestId){cons
440
440
  */
441
441
  async function viewBindingRoute(api,req,res,auth,ref,requestId){
442
442
  if(!['user','service'].includes(auth.kind))fail(403,'forbidden','credential cannot bind sessions');
443
- const session=await api.store.authorizedSession({teamId:auth.team_id,principalId:auth.principal_id,credentialId:auth.credential_id,ref,now:api.clock.nowMs()});
443
+ const session=await api.store.authorizedSession({teamId:auth.team_id,principalId:auth.principal_id,credentialId:auth.credential_id,ref,now:api.clock.nowMs(),forInstaller:true});
444
444
  const list=async()=>api.store.query('SELECT issuer,room,conversation,bound_by,created_at FROM session_view_bindings WHERE team_id=? AND session_id=? ORDER BY created_at',[auth.team_id,session.id]);
445
445
  if(req.method==='GET'){
446
446
  const bindings=await list();
@@ -28,7 +28,7 @@ export function createStoreHandlers({ all, get, run, tx }) {
28
28
  const {teamId,principalId,credentialId,ref,now,bound}=args;
29
29
  // `bound`: a workplace's session-view token ({issuer, room, conversation, expiresAt}), which reads only a
30
30
  // session its owner bound to that Room and Conversation; it holds no credential and is no member.
31
- let membership=null,appFence=null,grantSource=null,agentGrantSource=null,agentReader=false;
31
+ let membership=null,appFence=null,grantSource=null,agentGrantSource=null,agentReader=false,installerFor=null;
32
32
  if((args.delegationDepth??0)>16)domainError(401,'authentication_required','delegation lineage is too deep');
33
33
  if(bound){
34
34
  if(!get(`SELECT 1 FROM app_installations i JOIN apps a ON a.id=i.app_id JOIN memberships m ON m.team_id=i.team_id AND m.principal_id=i.principal_id JOIN memberships installer ON installer.team_id=i.team_id AND installer.principal_id=i.installer_id WHERE i.team_id=? AND a.issuer=? AND i.id=? AND i.created_at=? AND i.state='active' AND a.state='active' AND m.active=1 AND installer.active=1 AND installer.role IN ('owner','admin') AND EXISTS (SELECT 1 FROM json_each(i.actions_json) WHERE value='delegation.sessions')`,[teamId,bound.issuer,bound.installationId,bound.installedAt]))domainError(401,'authentication_required','integration was uninstalled');
@@ -38,11 +38,15 @@ export function createStoreHandlers({ all, get, run, tx }) {
38
38
  membership=get('SELECT role FROM memberships WHERE team_id=? AND principal_id=? AND active=1',[teamId,principalId]);
39
39
  const identity=get(`SELECT i.state,a.state app_state,m.active,m.role FROM identity_app_credentials c JOIN app_installations i ON i.id=c.installation_id JOIN apps a ON a.id=i.app_id JOIN memberships m ON m.team_id=i.team_id AND m.principal_id=i.installer_id WHERE c.credential_id=?`,[credentialId]);
40
40
  if(identity&&(identity.state!=='active'||identity.app_state!=='active'||!identity.active||!['owner','admin'].includes(identity.role)))domainError(401,'authentication_required','identity integration revoked');
41
- const app=get('SELECT ac.*,i.state,a.state app_state,installer.active,installer.role FROM app_credentials ac JOIN app_installations i ON i.id=ac.installation_id JOIN apps a ON a.id=i.app_id JOIN memberships installer ON installer.team_id=i.team_id AND installer.principal_id=i.installer_id WHERE ac.credential_id=?',[credentialId]);
41
+ const app=get('SELECT ac.*,i.state,i.installer_id,a.state app_state,installer.active,installer.role FROM app_credentials ac JOIN app_installations i ON i.id=ac.installation_id JOIN apps a ON a.id=i.app_id JOIN memberships installer ON installer.team_id=i.team_id AND installer.principal_id=i.installer_id WHERE ac.credential_id=?',[credentialId]);
42
42
  if(app){
43
43
  if(app.state!=='active'||app.app_state!=='active'||!app.active||!['owner','admin'].includes(app.role)||!JSON.parse(app.actions_json).includes('sessions.read'))domainError(403,'forbidden','app session read authorization changed');
44
44
  if(get(APP_LINEAGE_REVOKED,[credentialId,now]))domainError(401,'authentication_required','delegation authority revoked');
45
45
  appFence=JSON.parse(app.grants_json);
46
+ // `forInstaller` (the view-binding route only): an app its installer consented to `sessions.access` reaches the
47
+ // installer's own sessions to bind them, as that route already lets it act for them (ADR 0034). Every other read,
48
+ // a transcript, its messages and events, keeps the ordinary grants.
49
+ if(args.forInstaller&&appFence===null&&JSON.parse(app.actions_json).includes('sessions.access'))installerFor=app.installer_id;
46
50
  if(appFence!==null)grantSource=get('SELECT id,principal_id FROM credentials WHERE id=?',[app.subject_credential_id??app.parent_id]);
47
51
  }
48
52
  if(!credential||!membership)domainError(401,'authentication_required','authentication required');
@@ -72,6 +76,7 @@ export function createStoreHandlers({ all, get, run, tx }) {
72
76
  const readable=bound?Boolean(get('SELECT 1 ok FROM session_view_bindings WHERE team_id=? AND session_id=? AND issuer=? AND room=? AND conversation=?',[teamId,root.id,bound.issuer,bound.room,bound.conversation]))&&family.every(session=>session.deleted_at==null):family.every(session=>{
73
77
  if(session.deleted_at!=null)return false;
74
78
  if(!appGrantAllows({app_grants:appFence},'session',session.id,'sessions.read'))return false;
79
+ if(installerFor&&session.custodian_id===installerFor)return true;
75
80
  if(appFence!==null){
76
81
  if(!grantSource)return false;
77
82
  try{authorizedSession({teamId,principalId:grantSource.principal_id,credentialId:grantSource.id,ref:session.id,now,delegationDepth:(args.delegationDepth??0)+1,projectionDepth:1});}catch{return false;}