xuym-codex-collab 0.1.1 → 0.3.1
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/README.md +64 -109
- package/apps/onboarding/bundle.mjs +24 -0
- package/apps/onboarding/discovery.mjs +44 -0
- package/apps/onboarding/errors.mjs +1 -0
- package/apps/onboarding/main.mjs +32 -0
- package/apps/onboarding/manager.mjs +532 -0
- package/apps/onboarding/server.mjs +56 -0
- package/apps/onboarding/web/app.js +154 -0
- package/apps/onboarding/web/index.html +15 -0
- package/apps/onboarding/web/style.css +3 -0
- package/apps/peer/desktop.mjs +8 -6
- package/apps/peer/mcp.mjs +1 -1
- package/apps/peer/runner.mjs +5 -5
- package/apps/peer/runtime.mjs +4 -1
- package/apps/peer/wecom.mjs +76 -21
- package/bin/codex-collab.mjs +10 -7
- package/docs/architecture.md +3 -1
- package/docs/desktop-poll.md +16 -8
- package/docs/legacy-center.md +109 -0
- package/docs/local-automatic-test.md +1 -1
- package/docs/local-peer-test.md +1 -1
- package/docs/onboarding-audit.md +88 -0
- package/docs/onboarding.md +92 -69
- package/docs/peer.md +13 -7
- package/docs/protocol.md +2 -0
- package/docs/team-registry.md +65 -0
- package/docs/terminal-onboarding.md +98 -0
- package/package.json +6 -3
- package/packages/codex-kit/templates/.agents/skills/team-collab-desktop/SKILL.md +3 -3
- package/packages/peer/config.mjs +7 -3
- package/packages/peer/protocol.mjs +22 -7
- package/packages/peer/registry.mjs +133 -0
- package/packages/peer/review.mjs +52 -0
- package/packages/peer/service.mjs +24 -16
- package/packages/peer/store.mjs +59 -7
- package/schemas/encrypted-invite.schema.json +42 -0
- package/schemas/event.schema.json +51 -0
- package/schemas/peer-event.schema.json +44 -0
- package/schemas/peer-task.schema.json +21 -0
- package/schemas/task.schema.json +18 -0
- package/schemas/team-enrollment.schema.json +61 -0
- package/schemas/team-invitation.schema.json +108 -0
- package/schemas/team-policy.schema.json +182 -0
- package/schemas/team-version.schema.json +70 -0
- package/scripts/peer/cli.mjs +5 -2
- package/scripts/peer/onboarding.mjs +16 -4
package/packages/peer/store.mjs
CHANGED
|
@@ -5,18 +5,27 @@ import { join, resolve } from 'node:path';
|
|
|
5
5
|
import { randomUUID } from 'node:crypto';
|
|
6
6
|
import { digest, makeSigner, validateTeam } from './config.mjs';
|
|
7
7
|
import { reducePeerTask, taskIdPattern } from './protocol.mjs';
|
|
8
|
+
import { readRegistry, recordHash, registryPath } from './registry.mjs';
|
|
8
9
|
const exec = promisify(execFile);
|
|
9
10
|
const pause = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
10
11
|
export async function git(cwd, ...args) {
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
try {
|
|
13
|
+
const { stdout } = await exec('git', ['-c', 'core.hooksPath=/dev/null', '-C', cwd, ...args], { env: { ...process.env, GIT_TERMINAL_PROMPT: '0' }, timeout: 30000, maxBuffer: 4 * 1024 * 1024 });
|
|
14
|
+
return stdout.trim();
|
|
15
|
+
} catch (error) {
|
|
16
|
+
// Keep stderr available to callers that can turn it into a safe, actionable message.
|
|
17
|
+
// Never expose the raw value directly to the browser: it may contain credentials.
|
|
18
|
+
error.git_args = args;
|
|
19
|
+
throw error;
|
|
20
|
+
}
|
|
13
21
|
}
|
|
14
22
|
|
|
15
23
|
/** Disposable ledger clone, cross-process local lock, remote fast-forward CAS and signed replay. */
|
|
16
24
|
export class PeerGitStore {
|
|
17
|
-
constructor({ repo, dataDir, team, agentId, privateKey, clock = Date.now, maxRetries = 8 }) {
|
|
25
|
+
constructor({ repo, dataDir, team, agentId, privateKey, clock = Date.now, maxRetries = 8, registryRoot, enrollmentOnly = false }) {
|
|
18
26
|
this.repo = resolve(repo); this.dataDir = resolve(dataDir); this.checkout = join(this.dataDir, 'peer-ledger');
|
|
19
|
-
this.team = validateTeam(team); this.agentId = agentId; this.sign = makeSigner(team, agentId, privateKey);
|
|
27
|
+
this.team = validateTeam(team); this.agentId = agentId; this.enrollmentOnly = enrollmentOnly; this.sign = enrollmentOnly ? null : makeSigner(team, agentId, privateKey);
|
|
28
|
+
this.privateKey = privateKey; this.registryRoot = registryRoot; this.registry = null;
|
|
20
29
|
this.clock = clock; this.maxRetries = maxRetries; this.push = true;
|
|
21
30
|
}
|
|
22
31
|
async locked(fn) {
|
|
@@ -63,6 +72,23 @@ export class PeerGitStore {
|
|
|
63
72
|
await git(this.checkout, 'checkout', '-B', 'agent-collab', 'refs/remotes/origin/agent-collab');
|
|
64
73
|
await git(this.checkout, 'reset', '--hard', 'refs/remotes/origin/agent-collab');
|
|
65
74
|
await git(this.checkout, 'clean', '-fd');
|
|
75
|
+
if (this.registryRoot) {
|
|
76
|
+
const registry = await readRegistry(this.checkout, this.registryRoot);
|
|
77
|
+
const checkpointPath = join(this.dataDir, 'team-checkpoint.json');
|
|
78
|
+
let checkpoint;
|
|
79
|
+
try { checkpoint = JSON.parse(await readFile(checkpointPath, 'utf8')); }
|
|
80
|
+
catch (error) { if (error.code !== 'ENOENT') throw error; }
|
|
81
|
+
if (checkpoint && (registry.records.length < checkpoint.version || recordHash(registry.records[checkpoint.version - 1]) !== checkpoint.hash)) throw new Error('team registry rolled back or forked; refusing to trust it');
|
|
82
|
+
if (!this.enrollmentOnly) {
|
|
83
|
+
if (!registry.latest.team.agents[this.agentId]) throw new Error('member is not yet approved in the team registry');
|
|
84
|
+
this.sign = makeSigner(registry.latest.team, this.agentId, this.privateKey);
|
|
85
|
+
}
|
|
86
|
+
this.team = registry.latest.team; this.registry = registry;
|
|
87
|
+
const temporary = `${checkpointPath}.${process.pid}.tmp`;
|
|
88
|
+
await writeFile(temporary, JSON.stringify({ version: registry.latest.version, hash: recordHash(registry.latest) }) + '\n', { mode: 0o600 });
|
|
89
|
+
const { rename } = await import('node:fs/promises');
|
|
90
|
+
await rename(temporary, checkpointPath);
|
|
91
|
+
}
|
|
66
92
|
}
|
|
67
93
|
root(id) {
|
|
68
94
|
if (!taskIdPattern.test(id ?? '')) throw new Error('invalid peer task ID');
|
|
@@ -74,7 +100,9 @@ export class PeerGitStore {
|
|
|
74
100
|
const files = (await readdir(join(root, 'events'))).sort();
|
|
75
101
|
if (files.some((f) => !/^\d{6}-[a-f0-9-]{36}\.json$/.test(f))) throw new Error('unexpected peer event file');
|
|
76
102
|
const events = await Promise.all(files.map(async (file) => JSON.parse(await readFile(join(root, 'events', file), 'utf8'))));
|
|
77
|
-
|
|
103
|
+
const team = this.registry ? this.registry.teams.get(events[0]?.team_hash) : this.team;
|
|
104
|
+
if (!team) throw new Error('task references an untrusted team policy');
|
|
105
|
+
return { spec, events, state: reducePeerTask(spec, events, team), ...(this.registry ? { team } : {}) };
|
|
78
106
|
}
|
|
79
107
|
async listLocal() {
|
|
80
108
|
let entries;
|
|
@@ -85,7 +113,29 @@ export class PeerGitStore {
|
|
|
85
113
|
}
|
|
86
114
|
async list() { return this.locked(async () => { await this.sync(); return this.listLocal(); }); }
|
|
87
115
|
async read(id) { return this.locked(async () => { await this.sync(); return this.readLocal(id); }); }
|
|
116
|
+
async registryTransaction(build) {
|
|
117
|
+
return this.locked(async () => {
|
|
118
|
+
for (let attempt = 0; attempt < this.maxRetries; attempt++) {
|
|
119
|
+
await this.sync();
|
|
120
|
+
const result = await build(this.registry);
|
|
121
|
+
if (!result.files?.length) return result.value;
|
|
122
|
+
for (const [path, value] of result.files) {
|
|
123
|
+
if (!new RegExp(`^${registryPath.replace('.', '\\.')}/(?:versions/\\d{6}|(?:invitations|requests)/[a-f0-9-]{36})\\.json$`).test(path)) throw new Error('unsupported team registry file');
|
|
124
|
+
const target = join(this.checkout, path);
|
|
125
|
+
await mkdir(join(target, '..'), { recursive: true });
|
|
126
|
+
await writeFile(target, JSON.stringify(value, null, 2) + '\n', { flag: 'wx' });
|
|
127
|
+
await git(this.checkout, 'add', '--', path);
|
|
128
|
+
}
|
|
129
|
+
await git(this.checkout, 'commit', '-m', 'collab: update signed team enrollment');
|
|
130
|
+
try { await git(this.checkout, 'push', 'origin', 'HEAD:refs/heads/agent-collab'); }
|
|
131
|
+
catch { await pause(20 * (attempt + 1)); continue; }
|
|
132
|
+
await this.sync(); return result.value;
|
|
133
|
+
}
|
|
134
|
+
throw new Error('team enrollment not accepted; check Git access and retry');
|
|
135
|
+
});
|
|
136
|
+
}
|
|
88
137
|
async transact(id, idempotencyKey, build) {
|
|
138
|
+
if (this.enrollmentOnly) throw new Error('enrollment client cannot write tasks');
|
|
89
139
|
if (typeof idempotencyKey !== 'string' || !idempotencyKey || idempotencyKey.length > 300) throw new Error('invalid idempotency key');
|
|
90
140
|
return this.locked(async () => {
|
|
91
141
|
for (let attempt = 0; attempt < this.maxRetries; attempt++) {
|
|
@@ -102,10 +152,12 @@ export class PeerGitStore {
|
|
|
102
152
|
if (!built) return null;
|
|
103
153
|
const spec = current?.spec || built.spec;
|
|
104
154
|
if (!spec || spec.id !== id) throw new Error('transaction task mismatch');
|
|
105
|
-
const
|
|
155
|
+
const taskTeam = current?.team || this.team;
|
|
156
|
+
const sign = makeSigner(taskTeam, this.agentId, this.privateKey);
|
|
157
|
+
const event = sign({ schema_version: 2, task_id: id, seq: (current?.state.seq || 0) + 1,
|
|
106
158
|
event_id: randomUUID(), actor_id: this.agentId, occurred_at: new Date(now).toISOString(), idempotency_key: idempotencyKey,
|
|
107
159
|
...built.event, ...(current ? {} : { spec_hash: digest(spec) }) });
|
|
108
|
-
reducePeerTask(spec, [...(current?.events || []), event],
|
|
160
|
+
reducePeerTask(spec, [...(current?.events || []), event], taskTeam);
|
|
109
161
|
const root = this.root(id); await mkdir(join(root, 'events'), { recursive: true });
|
|
110
162
|
if (!current) await writeFile(join(root, 'spec.json'), JSON.stringify(spec, null, 2) + '\n', { flag: 'wx' });
|
|
111
163
|
await writeFile(join(root, 'events', `${String(event.seq).padStart(6, '0')}-${event.event_id}.json`), JSON.stringify(event, null, 2) + '\n', { flag: 'wx' });
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "urn:codex-collab:encrypted-invite:v1",
|
|
4
|
+
"title": "Codex Collab Encrypted Private Onboarding Package v1",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": true,
|
|
7
|
+
"required": [
|
|
8
|
+
"schema_version",
|
|
9
|
+
"kind",
|
|
10
|
+
"salt",
|
|
11
|
+
"iv",
|
|
12
|
+
"tag",
|
|
13
|
+
"ciphertext"
|
|
14
|
+
],
|
|
15
|
+
"properties": {
|
|
16
|
+
"schema_version": {
|
|
17
|
+
"const": 1
|
|
18
|
+
},
|
|
19
|
+
"kind": {
|
|
20
|
+
"const": "codex-collab-encrypted-invite"
|
|
21
|
+
},
|
|
22
|
+
"salt": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"contentEncoding": "base64"
|
|
25
|
+
},
|
|
26
|
+
"iv": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"contentEncoding": "base64"
|
|
29
|
+
},
|
|
30
|
+
"tag": {
|
|
31
|
+
"type": "string",
|
|
32
|
+
"contentEncoding": "base64"
|
|
33
|
+
},
|
|
34
|
+
"ciphertext": {
|
|
35
|
+
"type": "string",
|
|
36
|
+
"minLength": 1,
|
|
37
|
+
"maxLength": 500000,
|
|
38
|
+
"contentEncoding": "base64"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"$comment": "This file is private, not a Git ledger record. Runtime validates decoded salt=16 bytes, IV=12 bytes and tag=16 bytes; scrypt derives AES-256-GCM key from a separate password, then verifies signed history and invitation in the decrypted payload."
|
|
42
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "urn:codex-collab:event:v1",
|
|
4
|
+
"title": "Codex Collab Event v1",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": ["schema_version", "event_id", "task_id", "seq", "type", "actor_id", "occurred_at", "idempotency_key"],
|
|
8
|
+
"allOf": [{
|
|
9
|
+
"if": { "properties": { "type": { "const": "handoff" } }, "required": ["type"] },
|
|
10
|
+
"then": {
|
|
11
|
+
"required": ["summary", "refs"],
|
|
12
|
+
"properties": {
|
|
13
|
+
"summary": { "type": "string", "minLength": 1, "maxLength": 4000 },
|
|
14
|
+
"refs": {
|
|
15
|
+
"type": "object", "required": ["branch", "commit", "tests"],
|
|
16
|
+
"properties": {
|
|
17
|
+
"branch": { "type": "string", "pattern": "^codex/[A-Za-z0-9][A-Za-z0-9._/-]{0,150}$" },
|
|
18
|
+
"commit": { "type": "string", "pattern": "^[a-fA-F0-9]{40}([a-fA-F0-9]{24})?$" },
|
|
19
|
+
"tests": { "type": "string", "minLength": 1, "maxLength": 1000 }
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}],
|
|
25
|
+
"properties": {
|
|
26
|
+
"schema_version": { "const": 1 },
|
|
27
|
+
"event_id": { "type": "string", "minLength": 1 },
|
|
28
|
+
"task_id": { "type": "string", "pattern": "^[A-Z][A-Z0-9-]{1,63}$" },
|
|
29
|
+
"seq": { "type": "integer", "minimum": 1 },
|
|
30
|
+
"type": { "enum": ["created", "assigned", "run_started", "progress", "blocked", "run_failed", "handoff", "review_requested", "review_started", "review_note", "reviewed", "completed", "cancelled"] },
|
|
31
|
+
"actor_id": { "type": "string", "minLength": 1 },
|
|
32
|
+
"occurred_at": { "type": "string", "format": "date-time" },
|
|
33
|
+
"idempotency_key": { "type": "string", "minLength": 1 },
|
|
34
|
+
"summary": { "type": "string", "maxLength": 4000 },
|
|
35
|
+
"assignee": { "type": "string", "minLength": 1 },
|
|
36
|
+
"chat_id": { "type": "string", "minLength": 1 },
|
|
37
|
+
"channel": { "enum": ["feishu", "wecom"] },
|
|
38
|
+
"run_id": { "type": "string", "minLength": 1 },
|
|
39
|
+
"decision": { "enum": ["approved", "changes_requested"] },
|
|
40
|
+
"refs": {
|
|
41
|
+
"type": "object",
|
|
42
|
+
"additionalProperties": false,
|
|
43
|
+
"properties": {
|
|
44
|
+
"branch": { "type": "string" },
|
|
45
|
+
"commit": { "type": "string" },
|
|
46
|
+
"pr": { "type": "string" },
|
|
47
|
+
"tests": { "type": "string" }
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "urn:codex-collab:peer-event:v2",
|
|
4
|
+
"title": "Codex Collab Signed Peer Event v2",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": ["schema_version", "event_id", "task_id", "seq", "type", "actor_id", "occurred_at", "idempotency_key", "team_hash", "signature"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"schema_version": { "const": 2 },
|
|
10
|
+
"event_id": { "type": "string", "minLength": 1 },
|
|
11
|
+
"task_id": { "type": "string", "pattern": "^T-[A-Z0-9-]{1,62}$" },
|
|
12
|
+
"seq": { "type": "integer", "minimum": 1 },
|
|
13
|
+
"type": { "enum": ["created", "desktop_claimed", "claimed", "renewed", "progress", "checkpoint", "handoff", "stage_review_requested", "review_requested", "run_failed", "blocked", "reopened", "reviewed", "completed", "cancelled"] },
|
|
14
|
+
"actor_id": { "type": "string", "pattern": "^[a-zA-Z0-9_-]{1,64}$" },
|
|
15
|
+
"occurred_at": { "type": "string", "format": "date-time", "pattern": "Z$" },
|
|
16
|
+
"idempotency_key": { "type": "string", "minLength": 1 },
|
|
17
|
+
"team_hash": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
|
|
18
|
+
"signature": { "type": "string", "minLength": 1 },
|
|
19
|
+
"spec_hash": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
|
|
20
|
+
"epoch": { "type": "integer", "minimum": 1 },
|
|
21
|
+
"lease_id": { "type": "string", "minLength": 1 },
|
|
22
|
+
"expires_at": { "type": "string", "format": "date-time" },
|
|
23
|
+
"summary": { "type": "string", "maxLength": 4000 },
|
|
24
|
+
"next_stage": { "type": "integer", "minimum": 1 },
|
|
25
|
+
"decision": { "enum": ["approved", "changes_requested"] },
|
|
26
|
+
"refs": {
|
|
27
|
+
"type": "object", "additionalProperties": false, "required": ["branch", "commit", "tests"],
|
|
28
|
+
"properties": {
|
|
29
|
+
"branch": { "type": "string", "pattern": "^codex/[A-Za-z0-9][A-Za-z0-9._/-]{0,150}$" },
|
|
30
|
+
"commit": { "type": "string", "pattern": "^[a-fA-F0-9]{40}([a-fA-F0-9]{24})?$" },
|
|
31
|
+
"tests": { "type": "string", "minLength": 1, "maxLength": 1000, "pattern": "\\S" }
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"allOf": [
|
|
36
|
+
{ "if": { "properties": { "type": { "const": "created" } }, "required": ["type"] }, "then": { "required": ["spec_hash"] } },
|
|
37
|
+
{ "if": { "properties": { "type": { "enum": ["claimed", "renewed"] } }, "required": ["type"] }, "then": { "required": ["epoch", "lease_id", "expires_at"] } },
|
|
38
|
+
{ "if": { "properties": { "type": { "enum": ["checkpoint", "handoff", "stage_review_requested", "review_requested", "completed"] } }, "required": ["type"] }, "then": { "required": ["refs"] } },
|
|
39
|
+
{ "if": { "properties": { "type": { "enum": ["handoff", "stage_review_requested"] } }, "required": ["type"] }, "then": { "required": ["summary", "next_stage"], "properties": { "summary": { "minLength": 1, "pattern": "\\S" } } } },
|
|
40
|
+
{ "if": { "properties": { "type": { "const": "reviewed" } }, "required": ["type"] }, "then": { "required": ["decision"] } },
|
|
41
|
+
{ "if": { "properties": { "type": { "const": "reopened" } }, "required": ["type"] }, "then": { "required": ["summary"], "properties": { "summary": { "minLength": 1, "pattern": "\\S" } } } }
|
|
42
|
+
],
|
|
43
|
+
"$comment": "Runtime additionally verifies Ed25519 signatures, pinned team hash, event sequence, role/route/capacity, exact shared branch-tip SHA and state transitions. Manual desktop reservations have no lease and require manual continuation or cancellation. desktop_poll tasks use the claimed/renewed lease events and existing fencing; bound conversation IDs stay private, never in event fields or idempotency keys. Team policy v3 requires nonempty rejection feedback and a current expected_seq on human review writes; runtime checks these policy-dependent requirements."
|
|
44
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "urn:codex-collab:peer-task:v2",
|
|
4
|
+
"title": "Codex Collab Peer Task v2",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": ["schema_version", "id", "repo", "title", "base_branch", "acceptance", "workflow"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"schema_version": { "const": 2 },
|
|
10
|
+
"id": { "type": "string", "pattern": "^T-[A-Z0-9-]{1,62}$" },
|
|
11
|
+
"repo": { "type": "string", "minLength": 1 },
|
|
12
|
+
"title": { "type": "string", "minLength": 1, "maxLength": 200, "pattern": "\\S" },
|
|
13
|
+
"description": { "type": "string", "maxLength": 4000 },
|
|
14
|
+
"base_branch": { "const": "main" },
|
|
15
|
+
"execution_mode": { "enum": ["desktop", "automatic", "desktop_poll"] },
|
|
16
|
+
"acceptance": { "type": "array", "minItems": 1, "maxItems": 32, "items": { "type": "string", "minLength": 1, "maxLength": 1000, "pattern": "\\S" } },
|
|
17
|
+
"workflow": { "type": "array", "minItems": 1, "maxItems": 16, "items": { "type": "string", "pattern": "^[a-zA-Z0-9_-]{1,64}$" } },
|
|
18
|
+
"source": { "type": "object", "additionalProperties": false, "required": ["channel", "chat_id"], "properties": { "channel": { "const": "wecom" }, "chat_id": { "type": "string", "minLength": 1 } } }
|
|
19
|
+
},
|
|
20
|
+
"$comment": "Runtime additionally verifies repo identity and every workflow route against the pinned team policy. desktop_poll starts open and uses signed execution leases for existing-thread heartbeat workers; automatic SDK dispatch excludes that mode."
|
|
21
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "urn:codex-collab:task:v1",
|
|
4
|
+
"title": "Codex Collab Task v1",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": ["schema_version", "id", "repo", "title", "base_branch", "acceptance"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"schema_version": { "const": 1 },
|
|
10
|
+
"id": { "type": "string", "pattern": "^[A-Z][A-Z0-9-]{1,63}$" },
|
|
11
|
+
"repo": { "type": "string", "minLength": 1 },
|
|
12
|
+
"title": { "type": "string", "minLength": 1, "maxLength": 200 },
|
|
13
|
+
"description": { "type": "string" },
|
|
14
|
+
"base_branch": { "type": "string", "minLength": 1 },
|
|
15
|
+
"acceptance": { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 } },
|
|
16
|
+
"source": { "type": "object", "additionalProperties": { "type": "string" } }
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "urn:codex-collab:team-enrollment:v1",
|
|
4
|
+
"title": "Codex Collab Dual-Signed Enrollment Request v1",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": [
|
|
8
|
+
"schema_version",
|
|
9
|
+
"request_id",
|
|
10
|
+
"invite_id",
|
|
11
|
+
"agent_id",
|
|
12
|
+
"public_key",
|
|
13
|
+
"wecom_userid",
|
|
14
|
+
"chat_id",
|
|
15
|
+
"signature",
|
|
16
|
+
"invitation_signature"
|
|
17
|
+
],
|
|
18
|
+
"properties": {
|
|
19
|
+
"schema_version": {
|
|
20
|
+
"const": 1
|
|
21
|
+
},
|
|
22
|
+
"request_id": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"pattern": "^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$"
|
|
25
|
+
},
|
|
26
|
+
"invite_id": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"pattern": "^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$"
|
|
29
|
+
},
|
|
30
|
+
"agent_id": {
|
|
31
|
+
"type": "string",
|
|
32
|
+
"pattern": "^[a-zA-Z0-9_-]{1,64}$"
|
|
33
|
+
},
|
|
34
|
+
"public_key": {
|
|
35
|
+
"type": "string",
|
|
36
|
+
"minLength": 1
|
|
37
|
+
},
|
|
38
|
+
"wecom_userid": {
|
|
39
|
+
"type": [
|
|
40
|
+
"string",
|
|
41
|
+
"null"
|
|
42
|
+
],
|
|
43
|
+
"minLength": 1,
|
|
44
|
+
"maxLength": 200
|
|
45
|
+
},
|
|
46
|
+
"chat_id": {
|
|
47
|
+
"type": "string",
|
|
48
|
+
"minLength": 1,
|
|
49
|
+
"maxLength": 200
|
|
50
|
+
},
|
|
51
|
+
"signature": {
|
|
52
|
+
"type": "string",
|
|
53
|
+
"minLength": 1
|
|
54
|
+
},
|
|
55
|
+
"invitation_signature": {
|
|
56
|
+
"type": "string",
|
|
57
|
+
"minLength": 1
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"$comment": "Runtime checks both member and invitation claim signatures, invite identity, allowed chat, expiry and userid semantics. Admin approval consumes invite_id once in a team version."
|
|
61
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "urn:codex-collab:team-invitation:v1",
|
|
4
|
+
"title": "Codex Collab Signed Member Invitation v1",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": [
|
|
8
|
+
"schema_version",
|
|
9
|
+
"invite_id",
|
|
10
|
+
"agent_id",
|
|
11
|
+
"capabilities",
|
|
12
|
+
"bot_id",
|
|
13
|
+
"chat_ids",
|
|
14
|
+
"commands_enabled",
|
|
15
|
+
"prefer",
|
|
16
|
+
"claim_public_key",
|
|
17
|
+
"expires_at",
|
|
18
|
+
"team_hash",
|
|
19
|
+
"actor_id",
|
|
20
|
+
"signature"
|
|
21
|
+
],
|
|
22
|
+
"properties": {
|
|
23
|
+
"schema_version": {
|
|
24
|
+
"const": 1
|
|
25
|
+
},
|
|
26
|
+
"invite_id": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"pattern": "^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$"
|
|
29
|
+
},
|
|
30
|
+
"agent_id": {
|
|
31
|
+
"type": "string",
|
|
32
|
+
"pattern": "^[a-zA-Z0-9_-]{1,64}$"
|
|
33
|
+
},
|
|
34
|
+
"capabilities": {
|
|
35
|
+
"type": "array",
|
|
36
|
+
"minItems": 1,
|
|
37
|
+
"maxItems": 16,
|
|
38
|
+
"uniqueItems": true,
|
|
39
|
+
"items": {
|
|
40
|
+
"type": "string",
|
|
41
|
+
"pattern": "^[a-zA-Z0-9_-]{1,64}$"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"bot_id": {
|
|
45
|
+
"type": "string",
|
|
46
|
+
"minLength": 1,
|
|
47
|
+
"maxLength": 200
|
|
48
|
+
},
|
|
49
|
+
"chat_ids": {
|
|
50
|
+
"type": "array",
|
|
51
|
+
"minItems": 1,
|
|
52
|
+
"items": {
|
|
53
|
+
"type": "string",
|
|
54
|
+
"minLength": 1,
|
|
55
|
+
"maxLength": 200
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"commands_enabled": {
|
|
59
|
+
"type": "boolean"
|
|
60
|
+
},
|
|
61
|
+
"prefer": {
|
|
62
|
+
"type": "boolean"
|
|
63
|
+
},
|
|
64
|
+
"claim_public_key": {
|
|
65
|
+
"type": "string",
|
|
66
|
+
"minLength": 1
|
|
67
|
+
},
|
|
68
|
+
"expires_at": {
|
|
69
|
+
"type": "string",
|
|
70
|
+
"format": "date-time"
|
|
71
|
+
},
|
|
72
|
+
"team_hash": {
|
|
73
|
+
"type": "string",
|
|
74
|
+
"pattern": "^[a-f0-9]{64}$"
|
|
75
|
+
},
|
|
76
|
+
"actor_id": {
|
|
77
|
+
"type": "string",
|
|
78
|
+
"pattern": "^[a-zA-Z0-9_-]{1,64}$"
|
|
79
|
+
},
|
|
80
|
+
"signature": {
|
|
81
|
+
"type": "string",
|
|
82
|
+
"minLength": 1
|
|
83
|
+
},
|
|
84
|
+
"owner_userid": {
|
|
85
|
+
"type": "string",
|
|
86
|
+
"minLength": 1,
|
|
87
|
+
"maxLength": 200,
|
|
88
|
+
"description": "Optional owner pinned to the issuing administrator for adding their own independent agent."
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
"$comment": "Runtime verifies administrator signature under team_hash, Ed25519 claim key, expiry, and conflicts with existing members or assignments. No bot Secret or private keys are allowed here. A self-owned invitation pins owner_userid to the issuing administrator; enrollment must prove the same account. Existing invitations without this optional field remain valid.",
|
|
92
|
+
"allOf": [
|
|
93
|
+
{
|
|
94
|
+
"if": {
|
|
95
|
+
"required": [
|
|
96
|
+
"owner_userid"
|
|
97
|
+
]
|
|
98
|
+
},
|
|
99
|
+
"then": {
|
|
100
|
+
"properties": {
|
|
101
|
+
"commands_enabled": {
|
|
102
|
+
"const": true
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
]
|
|
108
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "urn:codex-collab:team-policy:v3",
|
|
4
|
+
"title": "Codex Collab Peer Team Policy v2/v3",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": true,
|
|
7
|
+
"required": [
|
|
8
|
+
"schema_version",
|
|
9
|
+
"repo",
|
|
10
|
+
"default_route",
|
|
11
|
+
"reviewer",
|
|
12
|
+
"agents",
|
|
13
|
+
"routes",
|
|
14
|
+
"policy"
|
|
15
|
+
],
|
|
16
|
+
"properties": {
|
|
17
|
+
"schema_version": {
|
|
18
|
+
"enum": [
|
|
19
|
+
2,
|
|
20
|
+
3
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
"repo": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"minLength": 1
|
|
26
|
+
},
|
|
27
|
+
"default_route": {
|
|
28
|
+
"type": "string",
|
|
29
|
+
"pattern": "^[a-zA-Z0-9_-]{1,64}$"
|
|
30
|
+
},
|
|
31
|
+
"reviewer": {
|
|
32
|
+
"type": "string",
|
|
33
|
+
"pattern": "^[a-zA-Z0-9_-]{1,64}$"
|
|
34
|
+
},
|
|
35
|
+
"agents": {
|
|
36
|
+
"type": "object",
|
|
37
|
+
"minProperties": 1,
|
|
38
|
+
"propertyNames": {
|
|
39
|
+
"type": "string",
|
|
40
|
+
"pattern": "^[a-zA-Z0-9_-]{1,64}$"
|
|
41
|
+
},
|
|
42
|
+
"additionalProperties": {
|
|
43
|
+
"type": "object",
|
|
44
|
+
"additionalProperties": true,
|
|
45
|
+
"required": [
|
|
46
|
+
"role",
|
|
47
|
+
"capabilities",
|
|
48
|
+
"max_concurrent_tasks",
|
|
49
|
+
"public_key"
|
|
50
|
+
],
|
|
51
|
+
"properties": {
|
|
52
|
+
"role": {
|
|
53
|
+
"enum": [
|
|
54
|
+
"admin",
|
|
55
|
+
"member"
|
|
56
|
+
]
|
|
57
|
+
},
|
|
58
|
+
"capabilities": {
|
|
59
|
+
"type": "array",
|
|
60
|
+
"items": {
|
|
61
|
+
"type": "string",
|
|
62
|
+
"pattern": "^[a-zA-Z0-9_-]{1,64}$"
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"max_concurrent_tasks": {
|
|
66
|
+
"type": "integer",
|
|
67
|
+
"minimum": 1,
|
|
68
|
+
"maximum": 16
|
|
69
|
+
},
|
|
70
|
+
"public_key": {
|
|
71
|
+
"type": "string",
|
|
72
|
+
"minLength": 1
|
|
73
|
+
},
|
|
74
|
+
"wecom_userid": {
|
|
75
|
+
"type": "string",
|
|
76
|
+
"minLength": 1
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
"routes": {
|
|
82
|
+
"type": "object",
|
|
83
|
+
"propertyNames": {
|
|
84
|
+
"type": "string",
|
|
85
|
+
"pattern": "^[a-zA-Z0-9_-]{1,64}$"
|
|
86
|
+
},
|
|
87
|
+
"additionalProperties": {
|
|
88
|
+
"type": "array",
|
|
89
|
+
"minItems": 1,
|
|
90
|
+
"uniqueItems": true,
|
|
91
|
+
"items": {
|
|
92
|
+
"type": "string",
|
|
93
|
+
"pattern": "^[a-zA-Z0-9_-]{1,64}$"
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
"policy": {
|
|
98
|
+
"type": "object",
|
|
99
|
+
"additionalProperties": true,
|
|
100
|
+
"required": [
|
|
101
|
+
"claim_timeout_ms",
|
|
102
|
+
"lease_ms",
|
|
103
|
+
"clock_skew_ms",
|
|
104
|
+
"max_attempts"
|
|
105
|
+
],
|
|
106
|
+
"properties": {
|
|
107
|
+
"claim_timeout_ms": {
|
|
108
|
+
"type": "integer",
|
|
109
|
+
"minimum": 10,
|
|
110
|
+
"maximum": 3600000
|
|
111
|
+
},
|
|
112
|
+
"lease_ms": {
|
|
113
|
+
"type": "integer",
|
|
114
|
+
"minimum": 100,
|
|
115
|
+
"maximum": 3600000
|
|
116
|
+
},
|
|
117
|
+
"clock_skew_ms": {
|
|
118
|
+
"type": "integer",
|
|
119
|
+
"minimum": 0,
|
|
120
|
+
"maximum": 60000
|
|
121
|
+
},
|
|
122
|
+
"max_attempts": {
|
|
123
|
+
"type": "integer",
|
|
124
|
+
"minimum": 1,
|
|
125
|
+
"maximum": 20
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
"review_rules": {
|
|
130
|
+
"type": "object",
|
|
131
|
+
"propertyNames": {
|
|
132
|
+
"pattern": "^[a-zA-Z0-9_-]{1,64}$"
|
|
133
|
+
},
|
|
134
|
+
"additionalProperties": {
|
|
135
|
+
"type": "object",
|
|
136
|
+
"additionalProperties": false,
|
|
137
|
+
"required": [
|
|
138
|
+
"required",
|
|
139
|
+
"reviewer",
|
|
140
|
+
"notify"
|
|
141
|
+
],
|
|
142
|
+
"properties": {
|
|
143
|
+
"required": {
|
|
144
|
+
"type": "boolean"
|
|
145
|
+
},
|
|
146
|
+
"reviewer": {
|
|
147
|
+
"type": "string",
|
|
148
|
+
"pattern": "^(owner|admin|member:[a-zA-Z0-9_-]{1,64})$"
|
|
149
|
+
},
|
|
150
|
+
"notify": {
|
|
151
|
+
"type": "string",
|
|
152
|
+
"pattern": "^(owner|reviewer|none|member:[a-zA-Z0-9_-]{1,64})$"
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
"$comment": "Runtime verifies distinct Ed25519 keys and valid owner IDs (several agents may share the same human owner), valid reviewer and route capabilities, and clock skew below half the lease. v3 requires a review rule for every route; specific members require a verified owner. v2 forbids review_rules so old clients cannot silently bypass gates.",
|
|
159
|
+
"allOf": [
|
|
160
|
+
{
|
|
161
|
+
"if": {
|
|
162
|
+
"properties": {
|
|
163
|
+
"schema_version": {
|
|
164
|
+
"const": 3
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
"then": {
|
|
169
|
+
"required": [
|
|
170
|
+
"review_rules"
|
|
171
|
+
]
|
|
172
|
+
},
|
|
173
|
+
"else": {
|
|
174
|
+
"not": {
|
|
175
|
+
"required": [
|
|
176
|
+
"review_rules"
|
|
177
|
+
]
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
]
|
|
182
|
+
}
|