@cairnkeep/cli 2.26.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.
Files changed (51) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/docs/README.md +4 -0
  3. package/docs/learning/CURRICULUM-MAP.md +1 -1
  4. package/docs/learning/FEATURE-GUIDE.md +7 -1
  5. package/docs/learning/lessons/L00-why-cairnkeep.md +1 -1
  6. package/docs/learning/lessons/L01-safe-trial.md +3 -3
  7. package/docs/learning/lessons/L02-installation.md +2 -2
  8. package/docs/learning/lessons/L03-first-project.md +1 -1
  9. package/docs/learning/lessons/L19-validated-skill-improvement.md +1 -1
  10. package/docs/learning/lessons/L20-mcp-tool-profiles.md +1 -1
  11. package/docs/learning/lessons/L21-context-packs.md +1 -1
  12. package/docs/learning/lessons/L22-native-windows.md +2 -2
  13. package/docs/learning/lessons/L23-guided-setup.md +1 -1
  14. package/docs/learning/lessons/L24-okf-exchange.md +1 -1
  15. package/docs/learning/lessons/L25-playbooks.md +1 -1
  16. package/docs/learning/lessons/L26-context-intelligence.md +1 -1
  17. package/docs/learning/lessons/L27-ecosystem-evidence.md +1 -1
  18. package/docs/learning/lessons/L28-security-assurance.md +1 -1
  19. package/docs/learning/lessons/L29-team-continuity.md +10 -1
  20. package/docs/learning/tracks/operator.md +3 -0
  21. package/docs/learning/video-scripts/V29-team-continuity.md +12 -2
  22. package/docs/operating.md +4 -0
  23. package/docs/privacy-and-data-flow.md +9 -0
  24. package/docs/releasing.md +6 -0
  25. package/docs/storage.md +9 -0
  26. package/docs/team-onboarding.md +4 -0
  27. package/docs/team-operations.md +127 -0
  28. package/docs/team.md +10 -4
  29. package/examples/eval/bundled-fake.json +1 -1
  30. package/mcp-memory-server/dist/team-audit.js +41 -0
  31. package/mcp-memory-server/dist/team-cli.js +68 -27
  32. package/mcp-memory-server/dist/team-errors.js +7 -0
  33. package/mcp-memory-server/dist/team-files.js +28 -0
  34. package/mcp-memory-server/dist/team-mcp.js +4 -3
  35. package/mcp-memory-server/dist/team-pages.js +40 -0
  36. package/mcp-memory-server/dist/team-store.js +98 -39
  37. package/package.json +1 -1
  38. package/scripts/cairn-cli.mjs +1 -1
  39. package/scripts/completion.sh +8 -3
  40. package/scripts/test-ci-sharding.sh +6 -1
  41. package/scripts/test-completion.sh +5 -2
  42. package/scripts/test-release-cache.mjs +16 -0
  43. package/scripts/test-security-baseline.sh +3 -0
  44. package/scripts/test-smoke-timings.mjs +19 -0
  45. package/scripts/test-team-docs.sh +16 -1
  46. package/scripts/test-team-installed.mjs +11 -0
  47. package/scripts/test-team-lab-env.mjs +9 -0
  48. package/scripts/test-team-operations.mjs +31 -0
  49. package/scripts/test-team-operations.sh +5 -0
  50. package/scripts/verify-published-release.mjs +11 -2
  51. package/scripts/windows-platform.mjs +1 -1
@@ -19,10 +19,10 @@ for shell in bash zsh fish; do
19
19
  grep -q 'capabilities' "$tmp/$shell"
20
20
  grep -q 'team' "$tmp/$shell"
21
21
  grep -q 'credential create set remove issue revoke' "$tmp/$shell"
22
- for team_command in connect disconnect init project member credential serve mcp list read search history propose proposals proposal-show review audit doctor backup restore memory-delete; do
22
+ for team_command in connect disconnect init project member credential serve mcp list read search history propose proposals proposal-show review audit doctor backup restore memory-delete audit-export audit-verify audit-archive; do
23
23
  grep -qF "$team_command" "$tmp/$shell"
24
24
  done
25
- for team_flag in connection client url token-file harness access apply yes; do
25
+ for team_flag in connection client url token-file harness access apply yes backup previous read-only page-size cursor; do
26
26
  grep -q -- "--$team_flag\|-l $team_flag" "$tmp/$shell"
27
27
  done
28
28
  grep -q 'list.*status.*enable.*disable.*reset.*logging\|list status enable disable reset logging' "$tmp/$shell"
@@ -163,6 +163,9 @@ done
163
163
  grep -qF "'team'" "$tmp/powershell"
164
164
  grep -qF "'credential','create','set','remove','issue','revoke'" "$tmp/powershell"
165
165
  grep -qF "'memory-delete'" "$tmp/powershell"
166
+ for value in audit-export audit-verify audit-archive --backup --previous --read-only --page-size --cursor; do
167
+ grep -qF -- "'$value'" "$tmp/powershell"
168
+ done
166
169
  grep -qF "'protocol'" "$tmp/powershell"
167
170
  grep -qF "'--trajectory'" "$tmp/powershell"
168
171
  grep -qF "'--codex-jsonl'" "$tmp/powershell"
@@ -0,0 +1,16 @@
1
+ import assert from 'node:assert/strict';
2
+ import { npmVerificationCommands } from './verify-published-release.mjs';
3
+ const commands = npmVerificationCommands('/private/disposable/install', '1.2.3');
4
+ assert.equal(commands.length, 2);
5
+ for (const args of commands) {
6
+ assert.equal(args[args.indexOf('--cache') + 1], '/private/disposable/install/.verification-cache');
7
+ assert(args.includes('--prefer-online'));
8
+ assert(args.includes('--prefer-offline=false'));
9
+ assert(args.includes('--offline=false'));
10
+ assert.equal(args[args.indexOf('--registry') + 1], 'https://registry.npmjs.org');
11
+ }
12
+ assert.equal(commands[0][0], 'install');
13
+ assert(commands[0].includes('@cairnkeep/cli@1.2.3'));
14
+ assert(commands[0].includes('--ignore-scripts'));
15
+ assert.deepEqual(commands[1].slice(0, 2), ['audit', 'signatures']);
16
+ console.log('PASS: disposable online npm verification retains signature audit and exact version');
@@ -3,3 +3,6 @@ set -euo pipefail
3
3
  ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
4
4
  node "$ROOT/scripts/verify-security-baseline.mjs"
5
5
  node "$ROOT/scripts/test-codeql-cohort.mjs"
6
+ node "$ROOT/scripts/test-release-cache.mjs"
7
+ node "$ROOT/scripts/test-smoke-timings.mjs"
8
+ node "$ROOT/scripts/test-team-lab-env.mjs"
@@ -0,0 +1,19 @@
1
+ import assert from 'node:assert/strict';
2
+ import { runSmokeChecks, smokeChecks } from '../mcp-memory-server/scripts/run-smoke-suite.mjs';
3
+ const output = [], calls = [];
4
+ let tick = 0;
5
+ const result = runSmokeChecks(smokeChecks.slice(0, 3), {
6
+ spawn: (_command, args) => { calls.push(args); return { status: calls.length === 2 ? 9 : 0 }; },
7
+ now: () => (tick += 10), write: value => output.push(value),
8
+ });
9
+ assert.equal(result, 9);
10
+ assert.equal(calls.length, 2);
11
+ const timings = output.filter(value => value.startsWith('SMOKE_TIMING ')).map(value => JSON.parse(value.slice(13)));
12
+ assert.equal(timings.length, 2);
13
+ assert.deepEqual(timings.map(row => row.status), [0, 9]);
14
+ assert(timings.every(row => row.duration_ms === 10 && row.schema_version === 1));
15
+ assert.deepEqual(Object.keys(timings[0]).sort(), ['duration_ms','name','schema_version','status']);
16
+ const errors=[];
17
+ assert.throws(()=>runSmokeChecks(smokeChecks.slice(0,1),{spawn:()=>{throw Error('synthetic spawn failure');},write:value=>errors.push(value),now:()=>0}));
18
+ assert.equal(JSON.parse(errors.find(value=>value.startsWith('SMOKE_TIMING ')).slice(13)).status,1);
19
+ console.log('PASS: smoke timings preserve execution order, early failure, status and payload-free output');
@@ -43,9 +43,24 @@ grep -q '^## Privacy and trust boundary$' "$lesson"
43
43
  grep -qF 'L29-team-continuity.md' docs/learning/README.md
44
44
  grep -qF 'L29-team-continuity.md' docs/learning/tracks/operator.md
45
45
  grep -qF '`cairn team`' docs/learning/CURRICULUM-MAP.md
46
- for command in connect disconnect init project member credential serve mcp proposals proposal-show propose review search read audit doctor backup restore memory-delete; do
46
+ for command in connect disconnect init project member credential serve mcp proposals proposal-show propose review search read audit doctor backup restore memory-delete audit-export audit-verify audit-archive; do
47
47
  node mcp-memory-server/dist/team-cli.js --help | grep -q "cairn team $command"
48
48
  done
49
+ for doc in docs/team.md docs/team-onboarding.md "$lesson" "$video"; do
50
+ grep -qF 'team-operations.md' "$doc"
51
+ done
52
+ for term in '2.27.0' 'audit-verify' 'audit-archive' 'read-only' 'capacity' 'schema' 'synthetic' 'integrity'; do
53
+ grep -qiF "$term" docs/team-operations.md
54
+ done
55
+ if grep -qiE 'next-minor|source-only|next.minor source feature' docs/team-operations.md; then
56
+ echo 'Released team operations must not be described as source-only' >&2
57
+ exit 1
58
+ fi
59
+ if grep -qiE 'no audit archival/compaction command|currently has no pagination' docs/team.md; then
60
+ echo 'Team reference must describe released paging and archival' >&2
61
+ exit 1
62
+ fi
63
+ grep -qF 'One successful segment check is not a claim' docs/team-operations.md
49
64
  node mcp-memory-server/dist/team-cli.js --help | grep -qF 'member remove SUBJECT --project ID'
50
65
  for doc in docs/team.md "$lesson" "$video"; do
51
66
  grep -qF 'cairn team member remove' "$doc"
@@ -44,6 +44,17 @@ try {
44
44
  await run(['disconnect', '--project-root', project, '--apply', '--yes']);
45
45
  assert.equal(readFileSync(config, 'utf8'), original, 'installed disconnect restores exact untouched original');
46
46
  assert.equal(readFileSync(tokenFile, 'utf8'), credential.token);
47
+ Object.assign(env,{CAIRN_TEAM_URL:`http://127.0.0.1:${server.address().port}`,CAIRN_TEAM_ORGANIZATION:'trial-org',CAIRN_TEAM_TOKEN_FILE:tokenFile});
48
+ assert.deepEqual(JSON.parse((await run(['list','--project','trial-project','--page-size','1'])).stdout),{items:[],next_cursor:null});
49
+ await new Promise(r=>{server.close(r);server.closeAllConnections();});server=undefined;
50
+ const backup=join(base,'backup.json'),archive=join(base,'audit.json');
51
+ await run(['backup','--data',data,'--read-only','--output',backup]);
52
+ const exported=JSON.parse((await run(['audit-export','--data',data,'--backup',backup,'--output',archive])).stdout);
53
+ const archival=['audit-archive','--data',data,'--backup',backup,'--input',archive,'--confirm',exported.archive_digest];
54
+ assert.equal(JSON.parse((await run(archival)).stdout).applied,false);
55
+ assert.equal(JSON.parse((await run([...archival,'--apply'])).stdout).applied,true);
56
+ assert.equal(JSON.parse((await run(['doctor','--data',data])).stdout).capacity.checkpoint.archive_digest,exported.archive_digest);
57
+ assert(existsSync(backup) && existsSync(archive));
47
58
  console.log('PASS: actual installed team plan/connect/client doctor/disconnect, preserved MCPs and no admin store');
48
59
  } finally {
49
60
  if (server) await new Promise(r => { server.close(r); server.closeAllConnections(); });
@@ -0,0 +1,9 @@
1
+ import assert from 'node:assert/strict';
2
+ import {teamLabEnvironment} from '../mcp-memory-server/scripts/team-lab-env.mjs';
3
+ const env=teamLabEnvironment('/private/lab',{CAIRN_TEAM:'1'},{Path:'system-path',SYSTEMROOT:'windows-root',ComSpec:'system-shell',WINDIR:'windows-dir',PATHEXT:'.EXE',PSModulePath:'system-modules',LOCALAPPDATA:'os-local',USERDOMAIN:'os-domain',CAIRN_TEAM_BASE_DIR:'production',CAIRN_LLM_API_KEY:'secret',PROVIDER_API_KEY:'secret',HOME:'real-home'});
4
+ assert.equal(env.PATH,'system-path');assert.equal(env.SystemRoot,'windows-root');assert.equal(env.ComSpec,'system-shell');assert.equal(env.WINDIR,'windows-dir');assert.equal(env.PATHEXT,'.EXE');
5
+ assert.equal(env.PSModulePath,'system-modules');assert.equal(env.LOCALAPPDATA,'os-local');assert.equal(env.USERDOMAIN,'os-domain');
6
+ for(const key of ['HOME','USERPROFILE','TEMP','TMP'])assert.equal(env[key],'/private/lab');
7
+ assert.equal(env.CAIRN_TEAM,'1');
8
+ for(const key of ['CAIRN_TEAM_BASE_DIR','CAIRN_LLM_API_KEY','PROVIDER_API_KEY'])assert.equal(env[key],undefined);
9
+ console.log('PASS: isolated team lab keeps Windows bootstrap variables, not production stores or provider secrets');
@@ -0,0 +1,31 @@
1
+ import assert from 'node:assert/strict';
2
+ import { mkdtempSync, readFileSync, existsSync, rmSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join, resolve } from 'node:path';
5
+ import { spawnSync } from 'node:child_process';
6
+ import {teamLabEnvironment} from '../mcp-memory-server/scripts/team-lab-env.mjs';
7
+
8
+ const lab=mkdtempSync(join(tmpdir(),'cairn-team-endurance-contract-'));
9
+ try {
10
+ const script=resolve('mcp-memory-server/scripts/run-team-continuity-soak.mjs');
11
+ const out=join(lab,'report.json');
12
+ const env=teamLabEnvironment(lab);
13
+ const run=(args,enabled=false)=>spawnSync(process.execPath,[script,...args],{env:{...env,...(enabled?{CAIRN_TEAM_SOAK:'1'}:{})},encoding:'utf8',timeout:60000,maxBuffer:1024*1024});
14
+ assert.equal(run(['--help']).status,0);
15
+ assert.notEqual(run(['--out',out]).status,0,'endurance must be explicitly enabled');
16
+ assert.equal(existsSync(out),false);
17
+ const result=run(['--cycles','24','--duration-ms','0','--out',out],true);
18
+ assert.equal(result.status,0,result.stderr);
19
+ const report=JSON.parse(readFileSync(out,'utf8'));
20
+ assert.equal(report.result,'PASS');assert.equal(report.cycles,24);
21
+ assert.equal(report.agent_identities,2);assert.equal(report.api_process_restart_verified,true);
22
+ assert.equal(report.unclean_restart_verified,true);assert.equal(report.restores,1);assert.equal(report.audit_archives,1);
23
+ assert(report.credential_rotations>=2);assert(report.revocations_verified>=4);
24
+ for(const name of ['human_consent_claim','model_quality_claim','native_harness_ui_verified','production_configuration_changed'])assert.equal(report[name],false);
25
+ assert.equal(report.synthetic_only,true);assert.equal(report.paid_model_calls,0);
26
+ const bytes=readFileSync(out);
27
+ assert.notEqual(run(['--cycles','4','--duration-ms','0','--out',out],true).status,0,'reports are exclusive and cannot overwrite evidence');
28
+ assert.deepEqual(readFileSync(out),bytes);
29
+ assert.equal(existsSync(join(lab,'.cairnkeep')),false,'synthetic test must not create a real user store');
30
+ console.log('PASS: opt-in synthetic endurance, two MCP processes, API crash/restart, rotation, archive/restore and exclusive reports');
31
+ } finally {rmSync(lab,{recursive:true,force:true});}
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
4
+ cd "$ROOT"
5
+ node scripts/test-team-operations.mjs
@@ -65,6 +65,14 @@ async function bytesFrom(url, maxBytes, headers = {}, blobRedirects = false) {
65
65
  const parse = bytes => JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
66
66
  const digest = bytes => `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
67
67
 
68
+ export function npmVerificationCommands(prefix, version) {
69
+ const fresh = ['--cache', join(prefix, '.verification-cache'), '--prefer-online', '--prefer-offline=false', '--offline=false', '--registry', 'https://registry.npmjs.org'];
70
+ return [
71
+ ['install', '--prefix', prefix, `@cairnkeep/cli@${version}`, '--ignore-scripts', '--no-audit', '--no-fund', ...fresh],
72
+ ['audit', 'signatures', '--prefix', prefix, ...fresh],
73
+ ];
74
+ }
75
+
68
76
  async function main(args) {
69
77
  if (args.length === 1 && ["--help", "-h"].includes(args[0])) { console.log(usage); return; }
70
78
  const options = optionsFrom(args);
@@ -95,12 +103,13 @@ async function main(args) {
95
103
  tarball: async url => bytesFrom(url, 64 * 1024 * 1024),
96
104
  verifyNpm: async tarball => {
97
105
  const prefix = join(scratch, "npm");
98
- run(npm, ["install", "--prefix", prefix, `@cairnkeep/cli@${expected.version}`, "--ignore-scripts", "--no-audit", "--no-fund", "--registry", "https://registry.npmjs.org"]);
106
+ const [install, audit] = npmVerificationCommands(prefix, expected.version);
107
+ run(npm, install);
99
108
  const lock = parse(readStableFile(join(prefix, "package-lock.json"), { label: "Verification install lock", maxBytes: 8 * 1024 * 1024 }).bytes);
100
109
  if (lock.packages?.["node_modules/@cairnkeep/cli"]?.integrity !== `sha512-${createHash("sha512").update(tarball).digest("base64")}`) {
101
110
  throw new Error("Release verification failed: audited package/candidate binding.");
102
111
  }
103
- const audited = run(npm, ["audit", "signatures", "--prefix", prefix, "--registry", "https://registry.npmjs.org"]);
112
+ const audited = run(npm, audit);
104
113
  return /[1-9][0-9]* packages have verified registry signatures/.test(audited)
105
114
  && /[1-9][0-9]* packages have verified attestations/.test(audited);
106
115
  },
@@ -779,7 +779,7 @@ export function powershellCompletion() {
779
779
  $pack = 'init','lock','validate','install','import-okf','validate-okf','export-okf','list','show','remove','enable','disable','update','skills','approve-skill','revoke-skill','doctor','--repair','--project','--project-id','--json'
780
780
  $proposals = 'create','list','show','apply','doctor','--session','--scope','--model','--category','--project','--json'
781
781
  $security = 'doctor','--project','--json'
782
- $team = 'connect','disconnect','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','--connection','--client','--url','--token-file','--harness','--access','--apply','--yes'
782
+ $team = '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'
783
783
  $evaluation = 'validate','run','ablate','report','prune','delete','protocol','--task-set','--adapter','--output','--repetitions','--seed','--yes','--json','--disable','--experiment','--older-than-days','--dry-run','--trajectory','--codex-jsonl','--capture-authorized'
784
784
  $candidates = if ($commandAst.ToString() -match '^\\s*cairn\\s+setup(?:\\s|$)') { $setup } elseif ($commandAst.ToString() -match '^\\s*cairn\\s+team(?:\\s|$)') { $team } elseif ($commandAst.ToString() -match '^\\s*cairn\\s+playbook(?:\\s|$)') { $playbook } elseif ($commandAst.ToString() -match '^\\s*cairn\\s+pack(?:\\s|$)') { $pack } elseif ($commandAst.ToString() -match '^\\s*cairn\\s+proposals(?:\\s|$)') { $proposals } elseif ($commandAst.ToString() -match '^\\s*cairn\\s+security(?:\\s|$)') { $security } elseif ($commandAst.ToString() -match '^\\s*cairn\\s+eval(?:\\s|$)') { $evaluation } else { $commands }
785
785
  $candidates | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {