@volter/teams 0.2.14 → 0.2.16

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.2.14",
3
+ "version": "0.2.16",
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",
@@ -139,6 +139,21 @@ export async function agentSessionRoute(ctx){
139
139
  const receipt=await sendAgentMail(api,auth,decodeURIComponent(match[1]),body.text,req.headers['idempotency-key']);
140
140
  await send(res,200,result(api,auth,receipt),requestId);return true;
141
141
  }
142
+ if((match=/^\/machines\/([^/]+)\/agent-sessions$/.exec(sub))&&req.method==='POST'){
143
+ // A session this machine captured is declared its agent's by the machine itself, from the agent its launch named
144
+ // (the session's environment): the machine's custodian holds the session, so it records the lineage when it manages
145
+ // the agent, exactly as a declaration through /agents/{id}/sessions does.
146
+ // `main` is the launch's (SUPERCODE_AGENT_MAIN, chosen by the launcher): the custodian may set it through
147
+ // /agents/{id}/sessions already, so the machine acting for its custodian gains no authority by carrying it.
148
+ if(auth.kind!=='machine'||auth.machine_id!==match[1])fail(403,'forbidden','the owning machine declares its sessions\' agents');
149
+ const body=await parseJson(req);exact(body,['session','agent','main']);
150
+ if(typeof body.session!=='string'||typeof body.agent!=='string'||body.main!==undefined&&typeof body.main!=='boolean')fail(400,'invalid_request','session, agent and optional main boolean are required');
151
+ const agent=await api.store.one('SELECT * FROM agents WHERE team_id=? AND id=?',[auth.team_id,body.agent]);
152
+ if(!agent)fail(404,'not_found','agent not found');
153
+ if(!await manageableAgent(api,auth,agent))fail(403,'forbidden','only the agent\'s custodian, parent or sponsor declares its sessions');
154
+ const {session,guards:machineGuards}=await machineProfileSession(api,auth,body.session);
155
+ await declareOwnership(ctx,agent,session,{main:body.main},machineGuards);return true;
156
+ }
142
157
  if((match=/^\/machines\/([^/]+)\/session-profiles$/.exec(sub))&&req.method==='POST'){
143
158
  if(auth.kind!=='machine'||auth.machine_id!==match[1])fail(403,'forbidden','the owning machine reports resolved profiles');
144
159
  const body=await parseJson(req);exact(body,['session','profile','observed_at','observation_id']);
@@ -203,6 +218,12 @@ export async function agentSessionRoute(ctx){
203
218
  const body=await parseJson(req);exact(body,['session','main','starter_session']);
204
219
  if(typeof body.session!=='string'||body.main!==undefined&&typeof body.main!=='boolean')fail(400,'invalid_request','session reference and optional main boolean are required');
205
220
  const session=await api.store.authorizedSession({teamId:auth.team_id,principalId:auth.principal_id,credentialId:auth.credential_id,ref:body.session,now});
221
+ await declareOwnership(ctx,agent,session,body,[]);return true;
222
+ }
223
+ /** One session declared as one agent's: the session's custodian, who manages the agent, records it (rule 1). */
224
+ async function declareOwnership(ctx,agent,session,body,extraGuards){
225
+ const {api,res,auth,requestId,send,result,guardedMutation}=ctx;
226
+ const now=api.clock.nowMs();
206
227
  if(session.custodian_id!==auth.principal_id)fail(403,'forbidden','only your own session can be declared');
207
228
  const old=await api.store.one('SELECT * FROM agent_sessions WHERE team_id=? AND session_id=?',[auth.team_id,session.id]);
208
229
  if(old?.agent_id&&old.agent_id!==agent.id)fail(409,'conflict','a session already owned by an agent cannot change identity');
@@ -221,6 +242,6 @@ export async function agentSessionRoute(ctx){
221
242
  // The main session is an Agent field (ontology Agent.main_session): its change is the agent's change too.
222
243
  if(main&&agent.main_session_id!==session.id)statements.push(publication(api,auth,'agent.changed',agent.id,{...data.agent,revision:agent.revision+1},now));
223
244
  statements.push(stmt('UPDATE teams SET auth_epoch=auth_epoch+1 WHERE id=?',auth.team_id),publish(api,auth,session.id,data,now));
224
- await guardedMutation(api,res,auth,[...memberGuards(auth,now),ownerGuard(auth,session),...standing,...await managementGuards(api,auth,agent),revisionGuard(auth.team_id,session.id,old)],statements,{status:old?200:201,body:result(api,auth,data),targetId:session.id});
225
- send(res,old?200:201,result(api,auth,data),requestId);return true;
245
+ await guardedMutation(api,res,auth,[...memberGuards(auth,now),ownerGuard(auth,session),...standing,...await managementGuards(api,auth,agent),revisionGuard(auth.team_id,session.id,old),...extraGuards],statements,{status:old?200:201,body:result(api,auth,data),targetId:session.id});
246
+ send(res,old?200:201,result(api,auth,data),requestId);
226
247
  }
package/server/http.mjs CHANGED
@@ -67,7 +67,8 @@ export function createHttpHandler(api){return async(req,res)=>{const requestId=a
67
67
  if(req.method!=='GET')res._auditRecord={teamId:auth.team_id,actorId:auth.principal_id,act:auth.act??[],operation:`${req.method} ${path}`};
68
68
  if(['machine','ingest'].includes(auth.kind)){
69
69
  // a machine credential also publishes its own boards' events (the connector follows `workflow events`, D138 step 4)
70
- const machineDoor=auth.kind==='machine'&&(sub==='/machines/events'||sub==='/event-sources'||/^\/event-sources\/[^/]+\/events$/.test(sub)||/^\/machines\/[^/]+\/configuration$/.test(sub)||sub===`/machines/${auth.machine_id}/session-profiles`||sub.startsWith(`/machines/${auth.machine_id}/agent-closures`)||sub===`/machines/${auth.machine_id}/heartbeat`||sub.startsWith(`/machines/${auth.machine_id}/effects`)||new RegExp(`^/machines/${auth.machine_id}/attachments/[^/]+(?:/frames)?$`).test(sub));
70
+ // and declares the agent each of its sessions' launch named (agent-sessions, D138 step 5)
71
+ const machineDoor=auth.kind==='machine'&&(sub==='/machines/events'||sub==='/event-sources'||/^\/event-sources\/[^/]+\/events$/.test(sub)||/^\/machines\/[^/]+\/configuration$/.test(sub)||sub===`/machines/${auth.machine_id}/session-profiles`||sub===`/machines/${auth.machine_id}/agent-sessions`||sub.startsWith(`/machines/${auth.machine_id}/agent-closures`)||sub===`/machines/${auth.machine_id}/heartbeat`||sub.startsWith(`/machines/${auth.machine_id}/effects`)||new RegExp(`^/machines/${auth.machine_id}/attachments/[^/]+(?:/frames)?$`).test(sub));
71
72
  const sourceDoor=sub.startsWith(`/sources/${auth.source_id}/captures`)||sub.startsWith(`/sources/${auth.source_id}/uploads`);
72
73
  if(!machineDoor&&!sourceDoor)fail(403,'forbidden','credential kind cannot use this operation');
73
74
  }