@uuaid/agent-commons 0.0.0 → 0.2.0-alpha.4

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.
Files changed (78) hide show
  1. package/CONFORMANCE.md +26 -0
  2. package/LICENSE +201 -0
  3. package/NOTICE +22 -0
  4. package/PROVENANCE.json +24 -0
  5. package/README.md +404 -5
  6. package/bin/agent-commons.mjs +76 -0
  7. package/examples/Dockerfile +12 -0
  8. package/examples/agent.mjs +17 -0
  9. package/examples/global-policy.json +9 -0
  10. package/examples/run-cohort.mjs +68 -0
  11. package/package.json +31 -8
  12. package/profiles/agentnet.chat.json +11 -0
  13. package/profiles/local.json +11 -0
  14. package/profiles/zilligon.com.json +11 -0
  15. package/profiles/zilligons.com.json +11 -0
  16. package/src/carrier.mjs +84 -0
  17. package/src/channels.d.mts +75 -0
  18. package/src/channels.mjs +158 -0
  19. package/src/cohort.mjs +61 -0
  20. package/src/collaboration.mjs +875 -0
  21. package/src/config.mjs +40 -0
  22. package/src/continuity.d.mts +24 -0
  23. package/src/continuity.mjs +601 -0
  24. package/src/governance-scope.d.mts +46 -0
  25. package/src/governance-scope.mjs +139 -0
  26. package/src/governance.mjs +355 -0
  27. package/src/index.d.ts +111 -0
  28. package/src/index.mjs +15 -0
  29. package/src/loop.mjs +71 -0
  30. package/src/offline.d.mts +7 -0
  31. package/src/offline.mjs +29 -0
  32. package/src/pillar.mjs +59 -0
  33. package/src/profiles.d.mts +12 -0
  34. package/src/profiles.mjs +92 -0
  35. package/src/relay/auth.mjs +294 -0
  36. package/src/relay/board.mjs +223 -0
  37. package/src/relay/claims.mjs +25 -0
  38. package/src/relay/cli.mjs +49 -0
  39. package/src/relay/client.mjs +437 -0
  40. package/src/relay/docs/reconnect-v1.md +27 -0
  41. package/src/relay/docs/working-rules-v1.md +42 -0
  42. package/src/relay/enroll.mjs +232 -0
  43. package/src/relay/export-cli.mjs +47 -0
  44. package/src/relay/governance.mjs +1328 -0
  45. package/src/relay/mailbox.mjs +103 -0
  46. package/src/relay/records.mjs +146 -0
  47. package/src/relay/render.mjs +38 -0
  48. package/src/relay/roster.mjs +134 -0
  49. package/src/relay/routes.mjs +87 -0
  50. package/src/relay/schema.mjs +1081 -0
  51. package/src/relay/seed.mjs +89 -0
  52. package/src/relay/serve.mjs +116 -0
  53. package/src/relay/server.mjs +633 -0
  54. package/src/relay/signing.mjs +693 -0
  55. package/src/relay/test-mode.mjs +73 -0
  56. package/src/runtime.mjs +359 -0
  57. package/src/spaces/manifest.mjs +245 -0
  58. package/src/spaces/pillar-group.mjs +12 -0
  59. package/src/spaces/policy.mjs +229 -0
  60. package/src/spaces/store.mjs +325 -0
  61. package/src/store.mjs +73 -0
  62. package/src/transport.d.mts +76 -0
  63. package/src/transport.mjs +267 -0
  64. package/src/trust.mjs +104 -0
  65. package/src/uuaid-namespace.d.mts +6 -0
  66. package/src/uuaid-namespace.mjs +48 -0
  67. package/src/vendor/pillar/blob/blob.mjs +508 -0
  68. package/src/vendor/pillar/crypto/e2e.mjs +97 -0
  69. package/src/vendor/pillar/groups-v2.mjs +1108 -0
  70. package/src/vendor/pillar/groups.mjs +175 -0
  71. package/src/vendor/pillar/identity/jcs.mjs +34 -0
  72. package/src/vendor/pillar/identity/keychain.mjs +241 -0
  73. package/src/vendor/pillar/identity/tier.mjs +130 -0
  74. package/src/vendor/pillar/net/carrier-client.mjs +219 -0
  75. package/src/vendor/pillar/net/delegation.mjs +209 -0
  76. package/src/vendor/pillar/net/envelope.mjs +258 -0
  77. package/src/vendor/pillar/net/refusals.mjs +129 -0
  78. package/src/vendor/pillar/net/seed-tier.mjs +185 -0
@@ -0,0 +1,11 @@
1
+ {
2
+ "namespace":"tenant/agentnet.chat",
3
+ "name":"AgentNet private conversations",
4
+ "scope":"tenant",
5
+ "quorum":2,
6
+ "fixtures":[
7
+ "Negotiate the communication profile before initiating a private agent conversation.",
8
+ "A private agent conversation requires the communication profile and verified agent identity.",
9
+ "Publish a signed contribution only after consent to share local benchmark fixtures."
10
+ ]
11
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "namespace":"local/fleet",
3
+ "name":"Local fleet utility",
4
+ "scope":"local",
5
+ "quorum":2,
6
+ "fixtures":[
7
+ "Run the local validation suite before accepting a communication profile.",
8
+ "A communication profile must pass the local validation suite before adoption.",
9
+ "The local validation suite checks every communication profile without changing identity policy."
10
+ ]
11
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "namespace":"tenant/zilligon.com",
3
+ "name":"Zilligon fleet communications",
4
+ "scope":"tenant",
5
+ "quorum":2,
6
+ "fixtures":[
7
+ "Route the build task to the code forge agent and require a signed delivery receipt.",
8
+ "Require a signed delivery receipt before marking the build task complete.",
9
+ "The code forge agent returns verifiable provenance with a signed delivery receipt."
10
+ ]
11
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "namespace":"tenant/zilligons.com",
3
+ "name":"Zilligons fleet communications",
4
+ "scope":"tenant",
5
+ "quorum":2,
6
+ "fixtures":[
7
+ "Route the build task to the code forge agent and require a signed delivery receipt.",
8
+ "Require a signed delivery receipt before marking the build task complete.",
9
+ "The code forge agent returns verifiable provenance with a signed delivery receipt."
10
+ ]
11
+ }
@@ -0,0 +1,84 @@
1
+ import { createServer } from "node:http";
2
+ import { open, envelopeSha, Keychain, jcs } from "./pillar.mjs";
3
+ import { CommonsStore } from "./store.mjs";
4
+ import { RegistryTrust } from "./trust.mjs";
5
+
6
+ export class CommonsCarrier {
7
+ constructor({store=new CommonsStore(),trust=new RegistryTrust(),keychain=null,publicUrl=null,onEnvelope=null}={}){
8
+ this.store=store;this.trust=trust;this.keychain=keychain;this.publicUrl=publicUrl;this.onEnvelope=onEnvelope;this.server=null;this.startedAt=Date.now();this.rates=new Map();
9
+ this.waiters=new Map();this.waitCount=0;
10
+ }
11
+ async listen(port=0,host="127.0.0.1"){
12
+ if(!["127.0.0.1","::1","localhost"].includes(host)&&(!this.publicUrl?.startsWith("https://")||this.trust.policy.mode!=="global"))throw new Error("Public bind requires global fail-closed policy and an HTTPS reverse-proxy URL");
13
+ if(!["127.0.0.1","::1","localhost"].includes(host)){
14
+ if(!this.keychain?._identity)throw new Error("Public carrier requires its own admitted agent identity");
15
+ await this.trust.authorize(this.keychain._identity.uuaid,this.keychain._identity.publicKeyHex,"commons:relay");
16
+ }
17
+ if(!this.store.verify())throw new Error("Carrier audit integrity failure");
18
+ this.server=createServer((req,res)=>void this.handle(req,res));
19
+ this.server.headersTimeout=15000;this.server.requestTimeout=15000;
20
+ await new Promise((resolve,reject)=>{this.server.once("error",reject);this.server.listen(port,host,resolve)});
21
+ return this.server.address();
22
+ }
23
+ json(res,status,body){res.writeHead(status,{"content-type":"application/json","cache-control":"no-store","x-content-type-options":"nosniff"});res.end(JSON.stringify(body))}
24
+ rate(ip){
25
+ const now=Date.now();if(this.rates.size>1000)this.rates.clear();
26
+ const bucket=this.rates.get(ip)??{start:now,count:0};if(now-bucket.start>60000){bucket.start=now;bucket.count=0}
27
+ bucket.count++;this.rates.set(ip,bucket);return bucket.count<=120;
28
+ }
29
+ async read(req){let size=0;const chunks=[];for await(const chunk of req){size+=chunk.length;if(size>512000)throw new Error("Payload exceeds bounded carrier limit");chunks.push(chunk)}return JSON.parse(Buffer.concat(chunks).toString())}
30
+ async waitFor(recipient,ms,res){
31
+ if(this.waitCount>=32)throw new Error("Long-poll concurrency budget exhausted");
32
+ this.waitCount++;
33
+ await new Promise(resolve=>{
34
+ const set=this.waiters.get(recipient)??new Set();this.waiters.set(recipient,set);
35
+ const done=()=>{clearTimeout(timer);set.delete(done);if(!set.size)this.waiters.delete(recipient);this.waitCount--;res.off("close",done);resolve()};
36
+ const timer=setTimeout(done,ms);set.add(done);res.once("close",done);
37
+ });
38
+ }
39
+ async handle(req,res){
40
+ try {
41
+ if(!this.rate(req.socket.remoteAddress))return this.json(res,429,{error:"rate-limited"});
42
+ const url=new URL(req.url,"http://localhost");
43
+ if(req.method==="GET"&&url.pathname==="/v1/health")return this.json(res,200,{ok:true,implementation:"Agent Commons",protocol:"uuaid-pillar-envelope/2",uptime:Math.round((Date.now()-this.startedAt)/1000),auditVerified:this.store.verify()});
44
+ if(req.method==="GET"&&url.pathname==="/.well-known/agent-commons")return this.json(res,200,{v:"agent-commons/1",role:this.onEnvelope?"host-client-and-carrier":"carrier",uuaid:this.keychain?._identity.uuaid??null,publicKey:this.keychain?._identity.publicKeyHex??null,transports:["pillar-carrier/v1"],mode:this.trust.policy.mode??"local",admission:"policy-gated-agent-identities",globalStandardization:"requires-IAASO-publication"});
45
+ if(req.method==="GET"&&url.pathname==="/v1/seed-info"){
46
+ if(!this.keychain||!this.publicUrl)return this.json(res,404,{error:"private-carrier"});
47
+ const doc={v:1,url:this.publicUrl,uuaid:this.keychain._identity.uuaid,publicKey:this.keychain._identity.publicKeyHex,startedAt:new Date(this.startedAt).toISOString(),firstSeen:new Date(this.startedAt).toISOString(),stats:{relayed:this.store.db.prepare("SELECT COUNT(*) AS n FROM envelopes").get().n,served:0,stored:this.store.db.prepare("SELECT COUNT(*) AS n FROM envelopes").get().n},now:new Date().toISOString()};
48
+ return this.json(res,200,{...doc,sig:this.keychain.sign(Buffer.from(jcs(doc))).toString("hex")});
49
+ }
50
+ if(req.method==="POST"&&url.pathname==="/v1/envelopes"){
51
+ const envelope=await this.read(req),valid=open(envelope);
52
+ if(!valid.ok)return this.json(res,400,{accepted:false,reason:valid.reason});
53
+ if(envelope.delegation!==undefined)return this.json(res,403,{accepted:false,reason:"delegated-admission-not-configured"});
54
+ const timestamp=Date.parse(envelope.createdAt);
55
+ if(!Number.isFinite(timestamp)||Math.abs(Date.now()-timestamp)>86400000)return this.json(res,400,{accepted:false,reason:"envelope-stale"});
56
+ await this.trust.authorize(envelope.sender,envelope.transportSignature.publicKey,"commons:relay");
57
+ if(this.onEnvelope&&envelope.recipient===this.keychain?._identity.uuaid)await this.onEnvelope(envelope);
58
+ const receipt=this.store.transaction(()=>{const receipt=this.store.retain(envelope,envelopeSha(envelope));if(!receipt.duplicate)this.store.append(`carrier-${envelope.id}`,{kind:"carrier-recorded",envelopeId:envelope.id,sender:envelope.sender,recipient:envelope.recipient,sha:receipt.sha});return receipt});
59
+ for(const done of [...(this.waiters.get(envelope.recipient)??[])])done();
60
+ return this.json(res,202,{accepted:true,...receipt,proof:"carrier-recorded-not-recipient-read"});
61
+ }
62
+ if(req.method==="GET"&&url.pathname.startsWith("/v1/inbox/")){
63
+ const uuaid=decodeURIComponent(url.pathname.slice("/v1/inbox/".length)),since=Number(url.searchParams.get("since")??0);
64
+ const pub=req.headers["x-pillar-pubkey"],ts=req.headers["x-pillar-ts"],sig=req.headers["x-pillar-sig"];
65
+ if(!Number.isSafeInteger(since)||since<0||typeof pub!=="string"||typeof ts!=="string"||typeof sig!=="string"||!/^[0-9a-f]{128}$/.test(sig)||Math.abs(Date.now()-Number(ts))>300000||!Number.isFinite(Number(ts)))return this.json(res,401,{error:"invalid-inbox-auth"});
66
+ const signed=Buffer.from(`GET /v1/inbox/${uuaid}?since=${since}\n${ts}`);
67
+ if(!Keychain.verifyDetached(pub,signed,Buffer.from(sig,"hex")))return this.json(res,401,{error:"bad-inbox-signature"});
68
+ await this.trust.authorize(uuaid,pub,"commons:message");
69
+ const wait=Number(url.searchParams.get("wait")??0);
70
+ if(!Number.isFinite(wait)||wait<0)return this.json(res,400,{error:"invalid-wait"});
71
+ if(!this.store.inbox(uuaid,since).length&&wait>0){
72
+ await this.waitFor(uuaid,Math.min(wait,25)*1000,res);
73
+ if(res.destroyed)return;
74
+ // A long poll may outlive its admission: re-check before handing over mail, so a key
75
+ // blocked (under any spelling) or removed during the wait gets the fresh-request refusal.
76
+ await this.trust.authorize(uuaid,pub,"commons:message");
77
+ }
78
+ return this.json(res,200,{envelopes:this.store.inbox(uuaid,since),now:Date.now()});
79
+ }
80
+ return this.json(res,404,{error:"not-found"});
81
+ } catch(error){return this.json(res,error.code?403:400,{accepted:false,reason:error.code??"invalid-request",message:error.message.slice(0,200)})}
82
+ }
83
+ async close(){for(const set of [...this.waiters.values()])for(const done of [...set])done();if(this.server){await new Promise(resolve=>this.server.close(resolve));this.server=null}}
84
+ }
@@ -0,0 +1,75 @@
1
+ export const CANONICAL_CHANNEL_REGEX: RegExp;
2
+ export const RESERVED_CHANNEL_PREFIX: RegExp;
3
+
4
+ export type ChannelClassification =
5
+ | "structured"
6
+ | "reserved-malformed"
7
+ | "legacy-allowed"
8
+ | "legacy-denied"
9
+ | "invalid";
10
+
11
+ export interface ParsedChannelUri {
12
+ readonly provider: string;
13
+ readonly channelType: string;
14
+ readonly channelId: string;
15
+ }
16
+
17
+ export interface ChannelClassificationResult {
18
+ readonly ok: boolean;
19
+ readonly classification: ChannelClassification;
20
+ readonly channelUri?: string;
21
+ readonly thread?: string;
22
+ readonly parsed?: ParsedChannelUri;
23
+ readonly error?: string;
24
+ }
25
+
26
+ export interface ChannelDefinition {
27
+ readonly visibility?: "public" | "private" | "invite_only";
28
+ readonly members?: readonly string[];
29
+ readonly allowForwarding?: boolean;
30
+ readonly metadata?: Record<string, any>;
31
+ }
32
+
33
+ export interface ChannelPolicy {
34
+ readonly channelsEnabled?: boolean;
35
+ readonly channels?: Record<string, ChannelDefinition>;
36
+ readonly tenantProfiles?: Record<string, string>;
37
+ readonly agents?: Record<string, { tenantId?: string; capabilities?: string[]; [key: string]: any }>;
38
+ [key: string]: any;
39
+ }
40
+
41
+ export function classifyThread(thread: unknown, policy?: ChannelPolicy): ChannelClassificationResult;
42
+
43
+ export function assertChannelSendAdmission(
44
+ channelUri: string,
45
+ sender: string,
46
+ recipients: string[],
47
+ policy?: ChannelPolicy
48
+ ): void;
49
+
50
+ export function assertChannelReceiveAdmission(
51
+ channelUri: string,
52
+ sender: string,
53
+ receiver: string,
54
+ policy?: ChannelPolicy
55
+ ): void;
56
+
57
+ export function assertForwardingPermission(
58
+ sourceChannelUri: string,
59
+ targetChannelUri: string,
60
+ sender: string,
61
+ policy?: ChannelPolicy
62
+ ): void;
63
+
64
+ export interface ProfileRef {
65
+ readonly id: string;
66
+ readonly scope: "local" | "tenant" | "global" | string;
67
+ [key: string]: any;
68
+ }
69
+
70
+ export function validateTenantBinding(
71
+ sender: string,
72
+ recipient: string,
73
+ profile: string | ProfileRef,
74
+ policy?: ChannelPolicy
75
+ ): void;
@@ -0,0 +1,158 @@
1
+ export const CANONICAL_CHANNEL_REGEX = /^channel:\/\/[a-z0-9._-]+\/[a-z0-9._-]+\/[a-zA-Z0-9._-]+$/;
2
+ export const RESERVED_CHANNEL_PREFIX = /^channel:/i;
3
+
4
+ /**
5
+ * Classifies an inbound or outbound thread parameter.
6
+ * Strictly prioritizes reserved-channel classification over legacy thread fallback.
7
+ */
8
+ export function classifyThread(thread, policy) {
9
+ if (typeof thread !== "string" || thread.length === 0 || thread.length > 120) {
10
+ return {
11
+ ok: false,
12
+ classification: "invalid",
13
+ error: "INVALID_THREAD_LENGTH"
14
+ };
15
+ }
16
+
17
+ // First precedence: Reserved channel classification
18
+ if (RESERVED_CHANNEL_PREFIX.test(thread)) {
19
+ if (!CANONICAL_CHANNEL_REGEX.test(thread)) {
20
+ return {
21
+ ok: false,
22
+ classification: "reserved-malformed",
23
+ error: "INVALID_CHANNEL_URI"
24
+ };
25
+ }
26
+ const [provider, channelType, channelId] = thread.slice("channel://".length).split("/");
27
+ return {
28
+ ok: true,
29
+ classification: "structured",
30
+ channelUri: thread,
31
+ parsed: { provider, channelType, channelId }
32
+ };
33
+ }
34
+
35
+ // Second precedence: Ordinary legacy thread handling
36
+ if (policy?.channelsEnabled === true) {
37
+ return {
38
+ ok: false,
39
+ classification: "legacy-denied",
40
+ error: "STRUCTURED_CHANNELS_REQUIRED"
41
+ };
42
+ }
43
+
44
+ return {
45
+ ok: true,
46
+ classification: "legacy-allowed",
47
+ thread
48
+ };
49
+ }
50
+
51
+ /**
52
+ * Asserts bidirectional admission for outbound channel messages.
53
+ * Both sender and every intended recipient must be members of the structured channel.
54
+ */
55
+ export function assertChannelSendAdmission(channelUri, sender, recipients, policy) {
56
+ const channel = policy?.channels?.[channelUri];
57
+ if (!channel) {
58
+ const err = new Error(`Channel is unconfigured: ${channelUri}`);
59
+ err.code = "UNCONFIGURED_CHANNEL";
60
+ throw err;
61
+ }
62
+
63
+ const members = new Set(channel.members ?? []);
64
+ if (!members.has(sender)) {
65
+ const err = new Error(`Sender ${sender} is unauthorized for channel ${channelUri}`);
66
+ err.code = "UNAUTHORIZED_CHANNEL_SENDER";
67
+ throw err;
68
+ }
69
+
70
+ for (const recipient of recipients) {
71
+ if (!members.has(recipient)) {
72
+ const err = new Error(`Recipient ${recipient} is unauthorized for channel ${channelUri}`);
73
+ err.code = "UNAUTHORIZED_CHANNEL_RECIPIENT";
74
+ throw err;
75
+ }
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Asserts bidirectional admission for inbound channel messages upon decryption.
81
+ * Both the decrypted sender and the local receiver must be members of the structured channel.
82
+ */
83
+ export function assertChannelReceiveAdmission(channelUri, sender, receiver, policy) {
84
+ const channel = policy?.channels?.[channelUri];
85
+ if (!channel) {
86
+ const err = new Error(`Channel is unconfigured: ${channelUri}`);
87
+ err.code = "UNCONFIGURED_CHANNEL";
88
+ throw err;
89
+ }
90
+
91
+ const members = new Set(channel.members ?? []);
92
+ if (!members.has(sender)) {
93
+ const err = new Error(`Sender ${sender} is unauthorized for channel ${channelUri}`);
94
+ err.code = "UNAUTHORIZED_CHANNEL_SENDER";
95
+ throw err;
96
+ }
97
+
98
+ if (!members.has(receiver)) {
99
+ const err = new Error(`Receiver ${receiver} is unauthorized for channel ${channelUri}`);
100
+ err.code = "UNAUTHORIZED_CHANNEL_RECEIVER";
101
+ throw err;
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Asserts explicit permission for cross-channel message forwarding.
107
+ * Sender requires 'commons:forward' and target channel must allow forwarding.
108
+ */
109
+ export function assertForwardingPermission(sourceChannelUri, targetChannelUri, sender, policy) {
110
+ const senderCaps = policy?.agents?.[sender]?.capabilities ?? [];
111
+ if (!senderCaps.includes("commons:forward")) {
112
+ const err = new Error(`Sender ${sender} lacks commons:forward capability`);
113
+ err.code = "CROSS_CHANNEL_FORWARDING_DENIED";
114
+ throw err;
115
+ }
116
+
117
+ const targetChannel = policy?.channels?.[targetChannelUri];
118
+ if (targetChannel?.allowForwarding !== true) {
119
+ const err = new Error(`Target channel ${targetChannelUri} does not permit forwarded messages`);
120
+ err.code = "CROSS_CHANNEL_FORWARDING_DENIED";
121
+ throw err;
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Validates tenant namespace binding between sender, recipient, and profile.
127
+ * Unbound tenant profiles fail closed with UNBOUND_PROFILE_TENANT.
128
+ * Unbound local profiles are permitted without error.
129
+ * Mismatches fail closed with TENANT_NAMESPACE_MISMATCH.
130
+ */
131
+ export function validateTenantBinding(sender, recipient, profileOrRef, policy) {
132
+ const profileId = typeof profileOrRef === "string" ? profileOrRef : profileOrRef?.id;
133
+ const scope = typeof profileOrRef === "object" && profileOrRef !== null ? profileOrRef.scope : undefined;
134
+
135
+ const boundTenant = policy?.tenantProfiles?.[profileId];
136
+ if (!boundTenant) {
137
+ if (scope !== "local") {
138
+ const err = new Error(`Unbound tenant profile: ${profileId}`);
139
+ err.code = "UNBOUND_PROFILE_TENANT";
140
+ throw err;
141
+ }
142
+ return;
143
+ }
144
+
145
+ const senderTenant = policy?.agents?.[sender]?.tenantId;
146
+ if (senderTenant !== boundTenant) {
147
+ const err = new Error(`Sender tenant mismatch: expected ${boundTenant}, got ${senderTenant}`);
148
+ err.code = "TENANT_NAMESPACE_MISMATCH";
149
+ throw err;
150
+ }
151
+
152
+ const recipientTenant = policy?.agents?.[recipient]?.tenantId;
153
+ if (recipientTenant !== boundTenant) {
154
+ const err = new Error(`Recipient tenant mismatch: expected ${boundTenant}, got ${recipientTenant}`);
155
+ err.code = "TENANT_NAMESPACE_MISMATCH";
156
+ throw err;
157
+ }
158
+ }
package/src/cohort.mjs ADDED
@@ -0,0 +1,61 @@
1
+ import { mkdirSync, existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join, resolve } from "node:path";
3
+ import { initialize, loadRuntime, saveConfig } from "./config.mjs";
4
+
5
+ export const FOUNDING_ROSTER = Object.freeze([
6
+ {id:"continuity",name:"Mneme",model:"claude_fable_5_1",label:"Claude Fable 5.1",provider:"Anthropic",role:"Continuity architect"},
7
+ {id:"governance",name:"Sol",model:"gpt_6_1_sol",label:"GPT 6.1 Sol",provider:"OpenAI",role:"Peer oversight engineer"},
8
+ {id:"collaboration",name:"Nexus",model:"claude_opus_5_5",label:"Claude Opus 5.5",provider:"Anthropic",role:"Collaboration runtime engineer"},
9
+ {id:"release",name:"Forge",model:"grok_4_7",label:"Grok 4.7",provider:"xAI",role:"Release and supply-chain engineer"},
10
+ {id:"integration",name:"Prism",model:"gemini_3_8_flash",label:"Gemini 3.8 Flash",provider:"Google",role:"Trust integration researcher"},
11
+ {id:"sustainability",name:"Terra",model:"gpt_5_6_terra",label:"GPT 5.6 Terra",provider:"OpenAI",role:"Sustainability and SOP steward"},
12
+ {id:"security",name:"Aegis",model:"claude_sonnet_5_5",label:"Claude Sonnet 5.5",provider:"Anthropic",role:"Independent safety reviewer"},
13
+ ].map(Object.freeze));
14
+
15
+ /** Generate seven private local identities. No remote registry or model call. */
16
+ export function initializeCohort({home,target="local",controllerId="local-operator"}={}){
17
+ if(typeof home!=="string"||!home)throw new Error("An explicit private cohort --home directory is required");
18
+ if(typeof controllerId!=="string"||controllerId.length<1||controllerId.length>120)throw new Error("Explicit controller identifier required");
19
+ home=resolve(home);
20
+ // Refuse partially populated destinations rather than silently clobber identities.
21
+ if(existsSync(join(home,"cohort.json"))||FOUNDING_ROSTER.some(a=>existsSync(join(home,a.id))))
22
+ throw new Error("Cohort destination already contains a manifest or member; identities were not overwritten");
23
+ mkdirSync(home,{recursive:true,mode:0o700});
24
+ const members=FOUNDING_ROSTER.map(member=>{
25
+ const nodeHome=join(home,member.id);
26
+ const initialized=initialize({home:nodeHome,target,name:member.name});
27
+ return {...member,home:nodeHome,uuaid:initialized.identity.uuaid,publicKey:initialized.identity.publicKeyHex,controllerId};
28
+ });
29
+ for(const member of members){
30
+ const node=loadRuntime(member.home);
31
+ node.config.policy.agents=Object.fromEntries(members.map(peer=>[peer.uuaid,{
32
+ kind:"agent",publicKey:peer.publicKey,controllerId,affiliations:[],
33
+ capabilities:peer.id==="security"?["commons:message","commons:relay"]:["commons:message","commons:relay","commons:evolve","commons:contribute"],
34
+ }]));
35
+ // Recovery is an explicit operator grant, not a default peer privilege.
36
+ saveConfig(member.home,node.config);node.store.close();
37
+ }
38
+ const manifest={version:1,product:"Agent Commons",target:target==="zilligons.com"?"zilligon.com":target,
39
+ members,registration:"not-performed",remoteMemory:"not-configured",certified:false,
40
+ ownerIndependence:"Single controller: seven keys do not establish independent owners",
41
+ modelAccess:"Configure an adapter; model selection alone is not provider access",
42
+ invitation:{autoJoin:false,autoBroadcast:false,consent:"explicit scoped admission required"}};
43
+ writeFileSync(join(home,"cohort.json"),JSON.stringify(manifest,null,2),{mode:0o600});
44
+ return manifest;
45
+ }
46
+
47
+ export function cohortStatus(home){
48
+ if(typeof home!=="string"||!home)throw new Error("Explicit cohort home required");
49
+ home=resolve(home);
50
+ const manifest=JSON.parse(readFileSync(join(home,"cohort.json"),"utf8"));
51
+ if(manifest.version!==1||manifest.members?.length!==7)throw new Error("Invalid cohort manifest");
52
+ const expected=new Set(FOUNDING_ROSTER.map(a=>a.id));
53
+ return {...manifest,members:manifest.members.map(member=>{
54
+ if(!expected.delete(member.id)||member.home!==join(home,member.id))throw new Error("Unsafe or duplicate cohort member path");
55
+ const node=loadRuntime(member.home);
56
+ try{
57
+ if(member.uuaid!==node.runtime.uuaid||member.publicKey!==node.runtime.publicKey)throw new Error("Cohort identity mismatch");
58
+ return {...member,status:node.runtime.status()};
59
+ }finally{node.store.close()}
60
+ })};
61
+ }