@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.
- package/CHANGELOG.md +23 -0
- package/docs/README.md +4 -0
- package/docs/learning/CURRICULUM-MAP.md +1 -1
- package/docs/learning/FEATURE-GUIDE.md +7 -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 +1 -1
- package/docs/learning/lessons/L29-team-continuity.md +10 -1
- package/docs/learning/tracks/operator.md +3 -0
- package/docs/learning/video-scripts/V29-team-continuity.md +12 -2
- package/docs/operating.md +4 -0
- package/docs/privacy-and-data-flow.md +9 -0
- package/docs/releasing.md +6 -0
- package/docs/storage.md +9 -0
- package/docs/team-onboarding.md +4 -0
- package/docs/team-operations.md +127 -0
- package/docs/team.md +10 -4
- package/examples/eval/bundled-fake.json +1 -1
- package/mcp-memory-server/dist/team-audit.js +41 -0
- package/mcp-memory-server/dist/team-cli.js +68 -27
- 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 +4 -3
- package/mcp-memory-server/dist/team-pages.js +40 -0
- package/mcp-memory-server/dist/team-store.js +98 -39
- package/package.json +1 -1
- package/scripts/cairn-cli.mjs +1 -1
- package/scripts/completion.sh +8 -3
- package/scripts/test-ci-sharding.sh +6 -1
- package/scripts/test-completion.sh +5 -2
- package/scripts/test-release-cache.mjs +16 -0
- package/scripts/test-security-baseline.sh +3 -0
- package/scripts/test-smoke-timings.mjs +19 -0
- package/scripts/test-team-docs.sh +16 -1
- package/scripts/test-team-installed.mjs +11 -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/verify-published-release.mjs +11 -2
- 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});}
|
|
@@ -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
|
-
|
|
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,
|
|
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 {
|