@cairnkeep/cli 2.25.0 → 2.27.0
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/CHANGELOG.md +64 -0
- package/docs/README.md +6 -0
- package/docs/harness-compatibility.md +5 -2
- package/docs/learning/CURRICULUM-MAP.md +3 -3
- package/docs/learning/FEATURE-GUIDE.md +13 -1
- package/docs/learning/lessons/L00-why-cairnkeep.md +1 -1
- package/docs/learning/lessons/L01-safe-trial.md +3 -3
- package/docs/learning/lessons/L02-installation.md +2 -2
- package/docs/learning/lessons/L03-first-project.md +1 -1
- package/docs/learning/lessons/L19-validated-skill-improvement.md +1 -1
- package/docs/learning/lessons/L20-mcp-tool-profiles.md +1 -1
- package/docs/learning/lessons/L21-context-packs.md +1 -1
- package/docs/learning/lessons/L22-native-windows.md +2 -2
- package/docs/learning/lessons/L23-guided-setup.md +1 -1
- package/docs/learning/lessons/L24-okf-exchange.md +1 -1
- package/docs/learning/lessons/L25-playbooks.md +1 -1
- package/docs/learning/lessons/L26-context-intelligence.md +1 -1
- package/docs/learning/lessons/L27-ecosystem-evidence.md +1 -1
- package/docs/learning/lessons/L28-security-assurance.md +5 -1
- package/docs/learning/lessons/L29-team-continuity.md +42 -1
- package/docs/learning/tracks/operator.md +5 -0
- package/docs/learning/video-scripts/V28-security-assurance.md +4 -0
- package/docs/learning/video-scripts/V29-team-continuity.md +29 -1
- package/docs/operating.md +10 -0
- package/docs/privacy-and-data-flow.md +30 -1
- package/docs/provider-data-flow-review.md +48 -0
- package/docs/releasing.md +6 -0
- package/docs/security-assurance.md +11 -0
- package/docs/storage.md +23 -0
- package/docs/team-onboarding.md +230 -0
- package/docs/team-operations.md +127 -0
- package/docs/team-pilot.md +2 -0
- package/docs/team.md +21 -4
- package/examples/eval/bundled-fake.json +1 -1
- package/mcp-memory-server/dist/capability-config.js +12 -1
- package/mcp-memory-server/dist/team-audit.js +41 -0
- package/mcp-memory-server/dist/team-cli.js +81 -30
- package/mcp-memory-server/dist/team-client.js +71 -0
- package/mcp-memory-server/dist/team-errors.js +7 -0
- package/mcp-memory-server/dist/team-files.js +28 -0
- package/mcp-memory-server/dist/team-mcp.js +12 -4
- package/mcp-memory-server/dist/team-pages.js +40 -0
- package/mcp-memory-server/dist/team-store.js +105 -40
- package/mcp-memory-server/package.json +2 -2
- package/package.json +1 -1
- package/scripts/cairn-cli.mjs +6 -1
- package/scripts/completion.sh +16 -3
- package/scripts/lib/team-retention.mjs +1 -1
- package/scripts/team-connect.mjs +366 -0
- package/scripts/test-ci-sharding.sh +6 -1
- package/scripts/test-codeql-cohort.mjs +35 -0
- package/scripts/test-completion.sh +7 -1
- package/scripts/test-package-install.sh +1 -0
- package/scripts/test-phase18-capability-lifecycle.sh +2 -2
- package/scripts/test-release-cache.mjs +16 -0
- package/scripts/test-security-baseline.sh +4 -0
- package/scripts/test-smoke-timings.mjs +19 -0
- package/scripts/test-team-docs.sh +26 -2
- package/scripts/test-team-installed.mjs +62 -0
- package/scripts/test-team-lab-env.mjs +9 -0
- package/scripts/test-team-operations.mjs +31 -0
- package/scripts/test-team-operations.sh +5 -0
- package/scripts/test-team-pilot.mjs +43 -1
- package/scripts/verify-published-release.mjs +11 -2
- package/scripts/verify-security-baseline.mjs +20 -0
- package/scripts/windows-platform.mjs +1 -1
|
@@ -3,6 +3,10 @@ import { closeSync, constants, existsSync, lstatSync, mkdirSync, mkdtempSync, op
|
|
|
3
3
|
import { basename, dirname, isAbsolute, join, parse, resolve } from 'node:path';
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
import { hardenPrivatePath, privatePathIsSafe } from './platform-security.js';
|
|
6
|
+
import { TeamError } from './team-errors.js';
|
|
7
|
+
export { TeamError } from './team-errors.js';
|
|
8
|
+
import { teamAuditCheckpointSchema, verifyTeamAuditRows, verifyTeamAuditArchive } from './team-audit.js';
|
|
9
|
+
import { teamPageSchema, pageTeamItems } from './team-pages.js';
|
|
6
10
|
const id = z.string().regex(/^[a-z0-9][a-z0-9-]{0,63}$/);
|
|
7
11
|
const roles = z.array(z.enum(['reader', 'contributor', 'reviewer', 'maintainer', 'auditor'])).max(5);
|
|
8
12
|
const digest = z.string().regex(/^[a-f0-9]{64}$/);
|
|
@@ -22,20 +26,21 @@ const adminSchema = z.discriminatedUnion('operation', [
|
|
|
22
26
|
z.object({ operation: z.literal('credential-revoke'), credential: z.string().uuid() }).strict(),
|
|
23
27
|
z.object({ operation: z.literal('memory-delete'), project: id, key: memoryKey, confirm: digest }).strict(),
|
|
24
28
|
]);
|
|
25
|
-
const
|
|
26
|
-
const snapshotSchema = z.
|
|
29
|
+
const legacySnapshotSchema = z.object({ schema_version: z.literal(1), organization: id, records: z.array(z.object({ organization: id, project: id.or(z.literal('')), kind: z.enum(['project', 'member', 'credential', 'proposal', 'review', 'revision', 'head']), id: z.string().min(1).max(128), value: z.unknown() }).strict()).max(32768), audit: z.array(z.object({ sequence: z.number().int().positive(), organization: id, project: id.or(z.literal('')), value: z.string().max(4096), digest }).strict()).max(100000), digest }).strict();
|
|
30
|
+
const snapshotSchema = z.discriminatedUnion('schema_version', [legacySnapshotSchema, legacySnapshotSchema.extend({ schema_version: z.literal(2), audit_checkpoint: teamAuditCheckpointSchema })]);
|
|
27
31
|
const MAX_RECORD_BYTES = 16 * 1024 * 1024;
|
|
28
32
|
const MAX_AUDIT_BYTES = 16 * 1024 * 1024;
|
|
29
33
|
export const TEAM_SNAPSHOT_MAX_BYTES = 64 * 1024 * 1024;
|
|
30
34
|
const requestBase = { organization: id, project: id };
|
|
31
35
|
const requestSchema = z.discriminatedUnion('operation', [
|
|
32
|
-
z.object({ ...requestBase, operation: z.literal('
|
|
33
|
-
z.object({ ...requestBase, operation: z.literal('
|
|
34
|
-
z.object({ ...requestBase, operation: z.literal('
|
|
36
|
+
z.object({ ...requestBase, operation: z.literal('connection-check') }).strict(),
|
|
37
|
+
z.object({ ...requestBase, operation: z.literal('list'), page: teamPageSchema.optional() }).strict(),
|
|
38
|
+
z.object({ ...requestBase, operation: z.literal('audit'), page: teamPageSchema.optional() }).strict(),
|
|
39
|
+
z.object({ ...requestBase, operation: z.literal('search'), page: teamPageSchema.optional(), query: z.string().min(1).max(256) }).strict(),
|
|
35
40
|
z.object({ ...requestBase, operation: z.literal('read'), key: memoryKey, revision: digest.optional() }).strict(),
|
|
36
|
-
z.object({ ...requestBase, operation: z.literal('history'), key: memoryKey }).strict(),
|
|
41
|
+
z.object({ ...requestBase, operation: z.literal('history'), page: teamPageSchema.optional(), key: memoryKey }).strict(),
|
|
37
42
|
z.object({ ...requestBase, operation: z.literal('propose'), ...proposalInput.shape }).strict(),
|
|
38
|
-
z.object({ ...requestBase, operation: z.literal('proposals') }).strict(),
|
|
43
|
+
z.object({ ...requestBase, operation: z.literal('proposals'), page: teamPageSchema.optional() }).strict(),
|
|
39
44
|
z.object({ ...requestBase, operation: z.literal('proposal-show'), proposal_id: z.string().uuid() }).strict(),
|
|
40
45
|
z.object({ ...requestBase, operation: z.literal('review'), proposal_id: z.string().uuid(), confirm: digest, decision: z.enum(['approve', 'reject']) }).strict(),
|
|
41
46
|
]);
|
|
@@ -54,13 +59,6 @@ async function immediate(db, operation) {
|
|
|
54
59
|
throw error;
|
|
55
60
|
}
|
|
56
61
|
}
|
|
57
|
-
export class TeamError extends Error {
|
|
58
|
-
code;
|
|
59
|
-
constructor(code) {
|
|
60
|
-
super(`Team ${code}.`);
|
|
61
|
-
this.code = code;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
62
|
export function teamDigest(text) { return createHash('sha256').update(text).digest('hex'); }
|
|
65
63
|
function checkedProposal(input) {
|
|
66
64
|
const value = valid(proposalSchema, input);
|
|
@@ -171,18 +169,7 @@ export function verifyTeamSnapshot(input) {
|
|
|
171
169
|
}
|
|
172
170
|
}
|
|
173
171
|
}
|
|
174
|
-
|
|
175
|
-
let sequence = 0;
|
|
176
|
-
let auditBytes = 0;
|
|
177
|
-
for (const row of snapshot.audit) {
|
|
178
|
-
const entry = valid(auditEntrySchema, JSON.parse(row.value));
|
|
179
|
-
auditBytes += Buffer.byteLength(row.value);
|
|
180
|
-
if (row.organization !== snapshot.organization || row.sequence !== ++sequence || entry.sequence !== sequence || entry.previous_digest !== previous || entry.organization !== row.organization || entry.project !== row.project || row.digest !== teamDigest(row.value))
|
|
181
|
-
throw new Error();
|
|
182
|
-
previous = row.digest;
|
|
183
|
-
}
|
|
184
|
-
if (auditBytes > MAX_AUDIT_BYTES + 256 * 1024)
|
|
185
|
-
throw new Error();
|
|
172
|
+
verifyTeamAuditRows(snapshot.organization, snapshot.audit, snapshot.schema_version === 2 ? snapshot.audit_checkpoint : null);
|
|
186
173
|
return snapshot;
|
|
187
174
|
}
|
|
188
175
|
catch {
|
|
@@ -300,7 +287,7 @@ export class TeamStore {
|
|
|
300
287
|
return options.organization;
|
|
301
288
|
});
|
|
302
289
|
const version = await db.prepare('SELECT value FROM team_meta WHERE id = ?').get('schema_version');
|
|
303
|
-
if (version?.value
|
|
290
|
+
if (!['1', '2'].includes(String(version?.value)))
|
|
304
291
|
throw new TeamError('integrity');
|
|
305
292
|
const store = new TeamStore(agent, org, root);
|
|
306
293
|
store.harden();
|
|
@@ -367,8 +354,9 @@ export class TeamStore {
|
|
|
367
354
|
async appendAudit(project, subject, credentialClass, operation, result, objectDigest = null) {
|
|
368
355
|
const db = this.agent.getDatabase();
|
|
369
356
|
const last = await db.prepare('SELECT sequence,digest FROM team_audit ORDER BY sequence DESC LIMIT 1').get();
|
|
370
|
-
const
|
|
371
|
-
const
|
|
357
|
+
const checkpoint = this.auditCheckpoint();
|
|
358
|
+
const sequence = valid(z.number().int().nonnegative(), last?.sequence ?? checkpoint?.sequence ?? 0) + 1;
|
|
359
|
+
const entry = { schema_version: 1, sequence, organization: this.organization, project, subject, credential_class: credentialClass, operation, result, object_digest: objectDigest, previous_digest: last?.digest ?? checkpoint?.digest ?? null, request_id: randomUUID(), at: new Date().toISOString() };
|
|
372
360
|
const value = JSON.stringify(entry);
|
|
373
361
|
const size = db.prepare('SELECT count(*) AS entries,coalesce(sum(length(CAST(value AS BLOB))),0) AS bytes FROM team_audit').get();
|
|
374
362
|
// Reserve bounded space for local revocation/recovery after a client's
|
|
@@ -459,9 +447,14 @@ export class TeamStore {
|
|
|
459
447
|
const project = valid(projectSchema, await this.get('project', request.project, request.project));
|
|
460
448
|
let result;
|
|
461
449
|
let objectDigest = null;
|
|
462
|
-
if (request.operation === '
|
|
463
|
-
|
|
464
|
-
|
|
450
|
+
if (request.operation === 'connection-check') {
|
|
451
|
+
result = { schema_version: 1, organization: this.organization, project: request.project,
|
|
452
|
+
subject: credential.subject, credential_id: credential.id, credential_class: credential.credential_class,
|
|
453
|
+
expires_at: credential.expires_at, roles: member.roles };
|
|
454
|
+
}
|
|
455
|
+
else if (request.operation === 'audit') {
|
|
456
|
+
const rows = await this.agent.getDatabase().prepare('SELECT value,digest FROM team_audit WHERE organization=? AND project=? ORDER BY sequence DESC' + (request.page ? '' : ' LIMIT 100')).all(this.organization, request.project);
|
|
457
|
+
result = (request.page ? rows : rows.slice(0, 100)).map(row => ({ ...JSON.parse(sqlText(row.value)), digest: row.digest }));
|
|
465
458
|
}
|
|
466
459
|
else if (request.operation === 'propose') {
|
|
467
460
|
const { organization: _org, project: _project, operation: _op, ...rawInput } = request;
|
|
@@ -525,7 +518,7 @@ export class TeamStore {
|
|
|
525
518
|
result = proposal;
|
|
526
519
|
}
|
|
527
520
|
else {
|
|
528
|
-
result = await Promise.all(visible.slice(0, 100).map(async (proposal) => ({ id: proposal.id, digest: proposal.digest, key: proposal.input.key, subject: proposal.subject, expires_at: proposal.input.expires_at, status: (await this.get('review', request.project, proposal.id)) ? 'reviewed' : 'pending' })));
|
|
521
|
+
result = await Promise.all(visible.slice(0, request.page ? undefined : 100).map(async (proposal) => ({ id: proposal.id, digest: proposal.digest, key: proposal.input.key, subject: proposal.subject, expires_at: proposal.input.expires_at, status: (await this.get('review', request.project, proposal.id)) ? 'reviewed' : 'pending' })));
|
|
529
522
|
}
|
|
530
523
|
}
|
|
531
524
|
else if (request.operation === 'read') {
|
|
@@ -541,17 +534,29 @@ export class TeamStore {
|
|
|
541
534
|
}
|
|
542
535
|
}
|
|
543
536
|
else if (request.operation === 'history') {
|
|
544
|
-
result = (await this.rows('revision', request.project)).map(row => checkedRevision(row.value)).filter(revision => revision.key === request.key).sort((a, b) => b.created_at.localeCompare(a.created_at) || a.revision_digest.localeCompare(b.revision_digest)).slice(0, 100).map(({ value: _value, ...metadata }) => metadata);
|
|
537
|
+
result = (await this.rows('revision', request.project)).map(row => checkedRevision(row.value)).filter(revision => revision.key === request.key).sort((a, b) => b.created_at.localeCompare(a.created_at) || a.revision_digest.localeCompare(b.revision_digest)).slice(0, request.page ? undefined : 100).map(({ value: _value, ...metadata }) => metadata);
|
|
545
538
|
}
|
|
546
539
|
else {
|
|
547
540
|
const heads = await this.rows('head', request.project);
|
|
548
541
|
const revisions = await Promise.all(heads.map(async (head) => checkedRevision(await this.get('revision', request.project, valid(digest, head.value)))));
|
|
549
542
|
if (request.operation === 'search') {
|
|
550
543
|
const needle = request.query.toLowerCase();
|
|
551
|
-
result = revisions.filter(revision => `${revision.key}\n${revision.value}`.toLowerCase().includes(needle)).slice(0, 10).map(revision => this.presentRevision(revision));
|
|
544
|
+
result = revisions.filter(revision => `${revision.key}\n${revision.value}`.toLowerCase().includes(needle)).slice(0, request.page ? undefined : 10).map(revision => this.presentRevision(revision));
|
|
552
545
|
}
|
|
553
546
|
else
|
|
554
|
-
result = revisions.slice(0, 100).map(({ value: _value, ...metadata }) => metadata);
|
|
547
|
+
result = revisions.slice(0, request.page ? undefined : 100).map(({ value: _value, ...metadata }) => metadata);
|
|
548
|
+
}
|
|
549
|
+
if ('page' in request && request.page) {
|
|
550
|
+
if (!Array.isArray(result) || (request.operation === 'search' && request.page.size > 10))
|
|
551
|
+
throw new TeamError('invalid');
|
|
552
|
+
const db = this.agent.getDatabase();
|
|
553
|
+
let secret = db.prepare('SELECT value FROM team_meta WHERE id=?').get('pagination-secret')?.value;
|
|
554
|
+
if (!secret) {
|
|
555
|
+
secret = randomBytes(32).toString('hex');
|
|
556
|
+
db.prepare('INSERT INTO team_meta(id,value) VALUES(?,?)').run('pagination-secret', secret);
|
|
557
|
+
}
|
|
558
|
+
const scope = JSON.stringify([this.organization, request.project, credential.id, credential.subject, credential.credential_class, member.roles, project.policy_revision, request.operation, 'key' in request ? request.key : null, 'query' in request ? request.query : null, request.page.size, this.auditCheckpoint()?.archive_digest ?? null]);
|
|
559
|
+
result = pageTeamItems(result, valid(digest, secret), scope, request.page, request.operation === 'audit');
|
|
555
560
|
}
|
|
556
561
|
await this.appendAudit(request.project, credential.subject, credential.credential_class, request.operation, 'accepted', objectDigest);
|
|
557
562
|
return result;
|
|
@@ -577,12 +582,68 @@ export class TeamStore {
|
|
|
577
582
|
const db = this.agent.getDatabase();
|
|
578
583
|
const records = db.prepare('SELECT organization,project,kind,id,value FROM team_records ORDER BY organization,project,kind,id').all().map(row => ({ organization: sqlText(row.organization), project: sqlText(row.project), kind: sqlText(row.kind), id: sqlText(row.id), value: JSON.parse(sqlText(row.value)) }));
|
|
579
584
|
const audit = db.prepare('SELECT sequence,organization,project,value,digest FROM team_audit ORDER BY sequence').all();
|
|
580
|
-
const
|
|
585
|
+
const checkpoint = this.auditCheckpoint();
|
|
586
|
+
const body = checkpoint ? { schema_version: 2, organization: this.organization, records, audit, audit_checkpoint: checkpoint } : { schema_version: 1, organization: this.organization, records, audit };
|
|
581
587
|
return verifyTeamSnapshot({ ...body, digest: teamDigest(JSON.stringify(body)) });
|
|
582
588
|
}
|
|
583
|
-
|
|
589
|
+
auditCheckpoint() {
|
|
590
|
+
const db = this.agent.getDatabase();
|
|
591
|
+
const version = db.prepare('SELECT value FROM team_meta WHERE id=?').get('schema_version')?.value;
|
|
592
|
+
const row = db.prepare('SELECT value FROM team_meta WHERE id=?').get('audit-checkpoint');
|
|
593
|
+
if (version === '1' && !row)
|
|
594
|
+
return null;
|
|
595
|
+
if (version !== '2' || !row)
|
|
596
|
+
throw new TeamError('integrity');
|
|
597
|
+
try {
|
|
598
|
+
return teamAuditCheckpointSchema.parse(JSON.parse(sqlText(row.value)));
|
|
599
|
+
}
|
|
600
|
+
catch {
|
|
601
|
+
throw new TeamError('integrity');
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
capacity() {
|
|
605
|
+
const db = this.agent.getDatabase();
|
|
606
|
+
const usage = (table, limit, bytesLimit) => {
|
|
607
|
+
const row = db.prepare(`SELECT count(*) AS entries,coalesce(sum(length(CAST(value AS BLOB))),0) AS bytes FROM ${table}`).get();
|
|
608
|
+
const entries = Number(row?.entries), bytes = Number(row?.bytes);
|
|
609
|
+
return { entries, bytes, limit, bytes_limit: bytesLimit, remaining_entries: Math.max(0, limit - entries), remaining_bytes: Math.max(0, bytesLimit - bytes), status: entries >= limit || bytes >= bytesLimit ? 'exhausted' : entries >= limit * 0.8 || bytes >= bytesLimit * 0.8 ? 'near-limit' : 'available' };
|
|
610
|
+
};
|
|
611
|
+
return { records: usage('team_records', 32768, MAX_RECORD_BYTES), audit: usage('team_audit', 99000, MAX_AUDIT_BYTES), local_audit_reserve: { entries: 1000, bytes: 256 * 1024 }, checkpoint: this.auditCheckpoint() };
|
|
612
|
+
}
|
|
613
|
+
async exportAudit(input) {
|
|
614
|
+
const backup = verifyTeamSnapshot(input);
|
|
615
|
+
return this.transaction(async () => {
|
|
616
|
+
const current = this.collectSnapshot();
|
|
617
|
+
if (current.digest !== backup.digest)
|
|
618
|
+
throw new TeamError('conflict');
|
|
619
|
+
const body = { schema_version: 1, organization: this.organization, backup_digest: backup.digest, previous_checkpoint: this.auditCheckpoint(), entries: current.audit };
|
|
620
|
+
return verifyTeamAuditArchive({ ...body, digest: teamDigest(JSON.stringify(body)) });
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
async archiveAudit(input, backupInput, confirm, apply = false) {
|
|
624
|
+
const archive = verifyTeamAuditArchive(input), backup = verifyTeamSnapshot(backupInput);
|
|
625
|
+
if (archive.digest !== confirm || archive.organization !== this.organization || archive.backup_digest !== backup.digest)
|
|
626
|
+
throw new TeamError('conflict');
|
|
584
627
|
return this.transaction(async () => {
|
|
585
|
-
|
|
628
|
+
const current = this.collectSnapshot();
|
|
629
|
+
if (current.digest !== backup.digest || JSON.stringify(current.audit) !== JSON.stringify(archive.entries) || JSON.stringify(this.auditCheckpoint()) !== JSON.stringify(archive.previous_checkpoint))
|
|
630
|
+
throw new TeamError('conflict');
|
|
631
|
+
const last = archive.entries[archive.entries.length - 1];
|
|
632
|
+
if (apply) {
|
|
633
|
+
const db = this.agent.getDatabase();
|
|
634
|
+
const checkpoint = { sequence: last.sequence, digest: last.digest, archive_digest: archive.digest, backup_digest: backup.digest, previous_archive_digest: archive.previous_checkpoint?.archive_digest ?? null };
|
|
635
|
+
db.prepare('INSERT INTO team_meta(id,value) VALUES(?,?) ON CONFLICT(id) DO UPDATE SET value=excluded.value').run('audit-checkpoint', JSON.stringify(checkpoint));
|
|
636
|
+
db.prepare('UPDATE team_meta SET value=? WHERE id=?').run('2', 'schema_version');
|
|
637
|
+
db.exec('DELETE FROM team_audit');
|
|
638
|
+
await this.appendAudit('', 'local-operator', 'local-os', 'audit-archive', 'accepted', archive.digest);
|
|
639
|
+
}
|
|
640
|
+
return { applied: apply, events: archive.entries.length, archive_digest: archive.digest, backup_digest: backup.digest, last_sequence: last.sequence };
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
async snapshot(options = {}) {
|
|
644
|
+
return this.transaction(async () => {
|
|
645
|
+
if (options.audit !== false)
|
|
646
|
+
await this.appendAudit('', 'local-operator', 'local-os', 'snapshot', 'accepted');
|
|
586
647
|
return this.collectSnapshot();
|
|
587
648
|
});
|
|
588
649
|
}
|
|
@@ -593,7 +654,7 @@ export class TeamStore {
|
|
|
593
654
|
const snapshot = this.collectSnapshot();
|
|
594
655
|
// Report only a count. Never select a restore, expose paths or delete remnants.
|
|
595
656
|
const remnants = readdirSync(dirname(this.root)).filter(name => name.startsWith('.team-restore-') || name === `.${basename(this.root)}.restore.lock`).length;
|
|
596
|
-
return { ok: true, records: snapshot.records.length, audit_events: snapshot.audit.length, digest: snapshot.digest, temporary_remnants: remnants };
|
|
657
|
+
return { ok: true, records: snapshot.records.length, audit_events: snapshot.audit.length, digest: snapshot.digest, temporary_remnants: remnants, capacity: this.capacity() };
|
|
597
658
|
});
|
|
598
659
|
}
|
|
599
660
|
static async restore(root, input, confirm) {
|
|
@@ -629,6 +690,10 @@ export class TeamStore {
|
|
|
629
690
|
const value = row.kind === 'credential' ? { ...valid(credentialSchema, row.value), revoked: true } : row.value;
|
|
630
691
|
db.prepare('INSERT INTO team_records(organization,project,kind,id,value) VALUES(?,?,?,?,?)').run(row.organization, row.project, row.kind, row.id, JSON.stringify(value));
|
|
631
692
|
}
|
|
693
|
+
if (snapshot.schema_version === 2) {
|
|
694
|
+
db.prepare('UPDATE team_meta SET value=? WHERE id=?').run('2', 'schema_version');
|
|
695
|
+
db.prepare('INSERT INTO team_meta(id,value) VALUES(?,?)').run('audit-checkpoint', JSON.stringify(snapshot.audit_checkpoint));
|
|
696
|
+
}
|
|
632
697
|
for (const row of snapshot.audit)
|
|
633
698
|
db.prepare('INSERT INTO team_audit(sequence,organization,project,value,digest) VALUES(?,?,?,?,?)').run(row.sequence, row.organization, row.project, row.value, row.digest);
|
|
634
699
|
await store.appendAudit('', 'local-operator', 'local-os', 'restore', 'accepted', snapshot.digest);
|
|
@@ -58,14 +58,14 @@
|
|
|
58
58
|
"check:context-pack-retrieval": "node scripts/smoke-context-pack-retrieval.mjs",
|
|
59
59
|
"check:context-pack-v216-compat": "node scripts/smoke-context-pack-v216-compat.mjs",
|
|
60
60
|
"check:context-usage": "node scripts/smoke-context-usage.mjs",
|
|
61
|
-
"check:domain-retrieval": "node scripts/smoke-domain-retrieval.mjs",
|
|
61
|
+
"check:domain-retrieval": "node scripts/smoke-domain-retrieval.mjs && node scripts/smoke-provider-config-flow.mjs",
|
|
62
62
|
"check:anythingllm-workspace": "node scripts/smoke-anythingllm-workspace.mjs && node scripts/smoke-anythingllm-workspace-mcp.mjs",
|
|
63
63
|
"check:retrieval-benchmark": "node scripts/smoke-retrieval-benchmark.mjs && node scripts/run-retrieval-benchmark.mjs --check",
|
|
64
64
|
"benchmark:retrieval": "npm run build && node scripts/run-retrieval-benchmark.mjs",
|
|
65
65
|
"check:work-evidence": "node scripts/smoke-work-evidence.mjs",
|
|
66
66
|
"check:okf": "node scripts/smoke-okf.mjs",
|
|
67
67
|
"check:playbook": "node scripts/smoke-playbook.mjs",
|
|
68
|
-
"check:security-assurance": "node scripts/smoke-security-
|
|
68
|
+
"check:security-assurance": "node scripts/smoke-security-fixture-order.mjs",
|
|
69
69
|
"check:notes": "npm run build && npm run check:note-signatures && npm run check:note-distillation && npm run check:note-enrichment",
|
|
70
70
|
"test": "npm run test:smoke",
|
|
71
71
|
"test:smoke": "npm run build && node scripts/run-smoke-suite.mjs",
|
package/package.json
CHANGED
package/scripts/cairn-cli.mjs
CHANGED
|
@@ -62,7 +62,7 @@ Usage:
|
|
|
62
62
|
cairn evidence <list|show|delete|prune|doctor>
|
|
63
63
|
cairn playbook <list|status|init|set|enable|disable|reset|check|record|receipts|instructions|doctor>
|
|
64
64
|
cairn security doctor [--project PATH] [--json]
|
|
65
|
-
cairn team <init|project|member|credential|serve|mcp|list|read|search|history|propose|proposals|proposal-show|review|audit|doctor|backup|restore|memory-delete>
|
|
65
|
+
cairn team <connect|disconnect|init|project|member|credential|serve|mcp|list|read|search|history|propose|proposals|proposal-show|review|audit|audit-export|audit-verify|audit-archive|doctor|backup|restore|memory-delete>
|
|
66
66
|
cairn memory <path|export|import>
|
|
67
67
|
cairn audit-timer [--on-calendar SPEC] [--para-root PATH] [--render-only DIR]
|
|
68
68
|
cairn uninstall [--dry-run] [--yes] [--purge-memory] [--purge-packs] [PROJECT ...]
|
|
@@ -96,6 +96,11 @@ export async function main(argv) {
|
|
|
96
96
|
return;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
if (command === 'team' && (['connect', 'disconnect'].includes(args[0]) || (args[0] === 'doctor' && args.includes('--client')))) {
|
|
100
|
+
const { runTeamConnection } = await import('./team-connect.mjs');
|
|
101
|
+
process.exitCode = await runTeamConnection(args);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
99
104
|
const nodeEntry = NODE_COMMANDS.get(command);
|
|
100
105
|
if (nodeEntry) {
|
|
101
106
|
// Preserve the historical dispatcher contract: child Node resolution comes
|
package/scripts/completion.sh
CHANGED
|
@@ -27,7 +27,7 @@ _cairn_complete() {
|
|
|
27
27
|
sync-omp) COMPREPLY=( $(compgen -W "--apply --live-root" -- "$current") ) ;;
|
|
28
28
|
doctor) COMPREPLY=( $(compgen -W "--repair" -- "$current") ) ;;
|
|
29
29
|
security) COMPREPLY=( $(compgen -W "doctor --project --json" -- "$current") ) ;;
|
|
30
|
-
team) COMPREPLY=( $(compgen -W "init project member credential create set remove issue revoke serve mcp list read search history propose proposals proposal-show review audit doctor backup restore memory-delete --organization --data --project --roles --class --expires-at --output --input --confirm --port --key --query --proposal --decision --revision --project-root" -- "$current") ) ;;
|
|
30
|
+
team) COMPREPLY=( $(compgen -W "connect disconnect init project member credential create set remove issue revoke serve mcp list read search history propose proposals proposal-show review audit audit-export audit-verify audit-archive doctor backup restore memory-delete --backup --previous --page-size --cursor --read-only --organization --data --project --roles --class --expires-at --output --input --confirm --port --key --query --proposal --decision --revision --project-root --connection --client --url --token-file --harness --access --apply --yes" -- "$current") ) ;;
|
|
31
31
|
trajectory) COMPREPLY=( $(compgen -W "list show prune --json --dry-run" -- "$current") ) ;;
|
|
32
32
|
artifact) COMPREPLY=( $(compgen -W "list show delete prune --kind --session --json --dry-run --include-protected" -- "$current") ) ;;
|
|
33
33
|
evidence) COMPREPLY=( $(compgen -W "list show delete prune doctor --status --json --dry-run --repair" -- "$current") ) ;;
|
|
@@ -134,7 +134,7 @@ _cairn() {
|
|
|
134
134
|
sync-omp) _arguments '--apply[apply changes]' '--live-root[omp agent root]:directory:_files -/' ;;
|
|
135
135
|
doctor) _arguments '--repair[repair trajectory metadata and indexes]' ;;
|
|
136
136
|
security) _values 'security command' doctor '--project[project root]:directory:_files -/' '--json[emit JSON]' ;;
|
|
137
|
-
team) _values 'team command' init project member credential create set remove issue revoke serve mcp list read search history propose proposals proposal-show review audit doctor backup restore memory-delete '--organization[team option]:value:' '--data[team option]:value:' '--project[team option]:value:' '--roles[team option]:value:' '--class[team option]:value:' '--expires-at[team option]:value:' '--output[team option]:value:' '--input[team option]:value:' '--confirm[team option]:value:' '--port[team option]:value:' '--key[team option]:value:' '--query[team option]:value:' '--proposal[team option]:value:' '--decision[team option]:value:' '--revision[team option]:value:' '--project-root[team option]:value:' ;;
|
|
137
|
+
team) _values 'team command' connect disconnect init project member credential create set remove issue revoke serve mcp list read search history propose proposals proposal-show review audit audit-export audit-verify audit-archive doctor backup restore memory-delete '--previous[previous archive segment]:file:_files' '--backup[verified backup file]:file:_files' '--page-size[page bound]:value:' '--cursor[continuation]:value:' '--read-only[observe-only backup]' '--organization[team option]:value:' '--data[team option]:value:' '--project[team option]:value:' '--roles[team option]:value:' '--class[team option]:value:' '--expires-at[team option]:value:' '--output[team option]:value:' '--input[team option]:value:' '--confirm[team option]:value:' '--port[team option]:value:' '--key[team option]:value:' '--query[team option]:value:' '--proposal[team option]:value:' '--decision[team option]:value:' '--revision[team option]:value:' '--apply[apply plan]' '--yes[confirm plan]' '--client[client diagnostics]' '--url[team URL]:value:' '--token-file[private credential file]:file:_files' '--harness[team adapters]:value:' '--access[tool profile]:value:(read-only full)' '--connection[private connection file]:file:_files' '--project-root[team option]:value:' ;;
|
|
138
138
|
trajectory) _values 'trajectory command' list show prune ;;
|
|
139
139
|
artifact)
|
|
140
140
|
case $words[3] in
|
|
@@ -222,7 +222,12 @@ complete -c cairn -n "__fish_seen_subcommand_from doctor" -l repair
|
|
|
222
222
|
complete -c cairn -n "__fish_seen_subcommand_from security" -a "doctor"
|
|
223
223
|
complete -c cairn -n "__fish_seen_subcommand_from security" -l project -r
|
|
224
224
|
complete -c cairn -n "__fish_seen_subcommand_from security" -l json
|
|
225
|
-
complete -c cairn -n "__fish_seen_subcommand_from team" -a "init project member credential create set remove issue revoke serve mcp list read search history propose proposals proposal-show review audit doctor backup restore memory-delete"
|
|
225
|
+
complete -c cairn -n "__fish_seen_subcommand_from team" -a "connect disconnect init project member credential create set remove issue revoke serve mcp list read search history propose proposals proposal-show review audit audit-export audit-verify audit-archive doctor backup restore memory-delete"
|
|
226
|
+
complete -c cairn -n "__fish_seen_subcommand_from team" -l previous -r
|
|
227
|
+
complete -c cairn -n "__fish_seen_subcommand_from team" -l backup -r
|
|
228
|
+
complete -c cairn -n "__fish_seen_subcommand_from team" -l page-size -r
|
|
229
|
+
complete -c cairn -n "__fish_seen_subcommand_from team" -l cursor -r
|
|
230
|
+
complete -c cairn -n "__fish_seen_subcommand_from team" -l read-only
|
|
226
231
|
complete -c cairn -n "__fish_seen_subcommand_from team" -l organization -r
|
|
227
232
|
complete -c cairn -n "__fish_seen_subcommand_from team" -l data -r
|
|
228
233
|
complete -c cairn -n "__fish_seen_subcommand_from team" -l project -r
|
|
@@ -239,6 +244,14 @@ complete -c cairn -n "__fish_seen_subcommand_from team" -l proposal -r
|
|
|
239
244
|
complete -c cairn -n "__fish_seen_subcommand_from team" -l decision -r
|
|
240
245
|
complete -c cairn -n "__fish_seen_subcommand_from team" -l revision -r
|
|
241
246
|
complete -c cairn -n "__fish_seen_subcommand_from team" -l project-root -r
|
|
247
|
+
complete -c cairn -n "__fish_seen_subcommand_from team" -l connection -r
|
|
248
|
+
complete -c cairn -n "__fish_seen_subcommand_from team" -l client
|
|
249
|
+
complete -c cairn -n "__fish_seen_subcommand_from team" -l url -r
|
|
250
|
+
complete -c cairn -n "__fish_seen_subcommand_from team" -l token-file -r
|
|
251
|
+
complete -c cairn -n "__fish_seen_subcommand_from team" -l harness -r -a "claude opencode qwen codex"
|
|
252
|
+
complete -c cairn -n "__fish_seen_subcommand_from team" -l access -r -a "read-only full"
|
|
253
|
+
complete -c cairn -n "__fish_seen_subcommand_from team" -l apply
|
|
254
|
+
complete -c cairn -n "__fish_seen_subcommand_from team" -l yes
|
|
242
255
|
complete -c cairn -n "__fish_seen_subcommand_from trajectory" -a "list show prune"
|
|
243
256
|
complete -c cairn -n "__fish_seen_subcommand_from artifact" -a "list show delete prune"
|
|
244
257
|
complete -c cairn -n "__fish_seen_subcommand_from artifact; and __fish_seen_subcommand_from list" -l kind -r
|
|
@@ -11,7 +11,7 @@ function canonical(path) {
|
|
|
11
11
|
|
|
12
12
|
/** Uninstall never grants permission to purge the separate team store. */
|
|
13
13
|
export function assertUninstallRetainsTeam(targets, env = process.env, home = homedir()) {
|
|
14
|
-
const stores = new Set([join(home, '.cairnkeep', 'team'), ...(env.CAIRN_TEAM_BASE_DIR ? [env.CAIRN_TEAM_BASE_DIR] : [])]);
|
|
14
|
+
const stores = new Set([join(home, '.cairnkeep', 'team'), join(home, '.cairnkeep', 'team-clients'), ...(env.CAIRN_TEAM_BASE_DIR ? [env.CAIRN_TEAM_BASE_DIR] : [])]);
|
|
15
15
|
const normalized = path => {
|
|
16
16
|
const value = canonical(path);
|
|
17
17
|
return process.platform === 'win32' ? value.toLowerCase() : value;
|